Getting Started with the Payin SDK

@paymentlabs/paymentlabs-js

A framework-agnostic TypeScript SDK for integrating Payment Labs payment widgets into your application.

What This SDK Provides

The Payment Labs SDK is a payment processing widget library that handles the payment collection portion of your checkout flow. Here's what it does and doesn't do:

What the SDK Provides:

  • Payment Method Widgets: Embedded payment forms for collecting credit cards, bank accounts, and other payment instruments
  • Secure Payment Processing: PCI-compliant payment tokenization and submission
  • Checkout Data Access: Retrieve payment details, amounts, merchant info, and payment status
  • Subscription Management: Handle recurring payment setup and subscription plan selection
  • Authentication Flows: Manage guest checkout, user login, and OTP verification
  • Event System: Real-time payment status updates and error handling
  • Theme Customization: Match your brand colors and styling

What the SDK Does NOT Provide:

  • Checkout UI: No cart display, product listings, or order summary components
  • E-commerce Logic: No inventory management, shipping calculation, or tax computation
  • Order Management: No order creation, fulfillment, or customer management
  • Payment Processing Backend: You need existing PaymentLabs checkout sessions

🏗️ Architecture Overview

Your Application                    Payment Labs SDK                   Payment Labs Backend
┌─────────────────┐                ┌─────────────────┐                ┌─────────────────┐
│ • Product Pages │                │ • Payment Widget│                │ • Checkout API  │
│ • Shopping Cart │   ┌────────►   │ • Event Handlers│   ┌────────►   │ • Authentication│
│ • Order Summary │   │            │ • Data Access   │   │            │ • Transaction   │
│ • Checkout UI   │   │            │ • Theme Support │   │            │   Processing    │
└─────────────────┘   │            └─────────────────┘   │            └─────────────────┘
         │             │                       │          │
         └─────────────┘                       └──────────┘
    
    You build the checkout experience    SDK handles payment collection    Payment Labs processes payments

Table of Contents

Installation

NPM/Yarn/PNPM

npm

npm install @paymentlabs/paymentlabs-js

yarn

yarn add @paymentlabs/paymentlabs-js

pnpm

pnpm add @paymentlabs/paymentlabs-js

CDN (Script Tag)

<script src="https://sdk.paymentlabs.io/payin/latest/paymentlabs.js"></script>
<script>
  // PaymentLabs is available globally
  const sdk = PaymentLabs.create({
    environment: 'sandbox',
    merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
  });
</script>

Specific Version

<!-- Use a specific version instead of 'latest' for production -->
<script src="https://sdk.paymentlabs.io/payin/v1.2.3/paymentlabs.js"></script>

Quick Start

Prerequisites

Before using the SDK, you need:

  1. Payment Labs Account
  2. Checkout: Create a checkout via Payment Labs API or dashboard
  3. Checkout ID: The unique identifier for your checkout (format: S-01ARZ3NDEKTSV4RRFFQ69G5FAV)

Creating Checkouts

The SDK requires pre-created checkouts from Payment Labs. Here's how to create them:

For One-time Payments (Checkout)

Your backend needs to create a checkout before your frontend can use the SDK.

API Reference: Create Checkout

For Recurring Payments (Subscriptions)

For subscription billing, you need to create subscription plans and plan offers using the Payment Labs API:

  1. Create Subscription Plan: Create Subscription Plan
  2. Create Plan Offer: Create Plan Offer

Use the Plan Offer ID with sdk.createWidget('subscription', offerId, ...).

Guide: Getting Started with Subscriptions

Complete Integration Example

Here's how the backend and frontend work together:

import { PaymentLabs, PaymentWidget } from '@paymentlabs/paymentlabs-js';

// Initialize the SDK
const sdk = PaymentLabs.create({
  environment: 'sandbox', // or 'production'
  merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
});

// Create a checkout widget
const widget = await sdk.createWidget('checkout', 'your-checkout-id', {
  containerId: 'checkout-container',
  theme: {
    colors: {
      seed: {
        brand: '#f44336',
        neutral: '#1f62e6'
      }
    }
  }
});

// Listen to events
widget.on('checkout-loaded', () => {
  const checkoutData = widget.getCheckoutData();
  console.log('Checkout loaded:', checkoutData);
});

// Submit payment
widget.submitPayment();

API Reference

PaymentLabs

The main SDK class for creating and managing payment widgets.

PaymentLabs.create(config)

Creates a new Payment Labs SDK instance.

Parameters:

  • config (object):
    • environment (string): The environment to use. Options: 'sandbox', 'production'
    • merchantId (string):

Returns: PaymentLabs instance

sdk.createWidget(type, id, options)

Creates a payment widget.

Parameters:

  • type (string): Widget type. Options: 'checkout', 'subscription'
  • id (string): Checkout ID or subscription offer ID
  • options (object):
    • containerId (string): ID of the HTML element where the widget will be rendered
    • theme (ThemeConfig, optional): Custom theme configuration

Returns: Promise

PaymentWidget

The widget instance that handles payment processing and events.

Methods

widget.on(event, callback)

Registers an event listener.

Parameters:

  • event (string): Event name
  • callback (function): Event handler function
widget.submitPayment()

Submits the payment for processing.

widget.getCheckoutData()

Retrieves the current checkout data.

Returns: Checkout | null

widget.getSubscriptionOfferData()

Retrieves the current subscription offer data.

Returns: SubscriptionOffer | null

Checkout Widget

The checkout widget handles one-time payments and payment method collection.

Basic Usage

import { PaymentLabs, PaymentWidget } from '@paymentlabs/paymentlabs-js';

class CheckoutManager {
  private widget: PaymentWidget | null = null;

  async initializeCheckout(checkoutId: string, containerId: string) {
    const sdk = PaymentLabs.create({
      environment: 'sandbox',
      merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
    });

    this.widget = await sdk.createWidget('checkout', checkoutId, {
      containerId,
      theme: this.getTheme()
    });

    this.setupEventListeners();
  }

  private setupEventListeners() {
    if (!this.widget) return;

    // Widget loaded
    this.widget.on('checkout-loaded', () => {
      const checkoutData = this.widget!.getCheckoutData();
      console.log('Checkout loaded:', checkoutData);
    });

    // Payment completed
    this.widget.on('checkout-completed', () => {
      const checkoutData = this.widget!.getCheckoutData();
      console.log('Payment completed:', checkoutData);
      this.handleSuccess();
    });

    // Payment failed
    this.widget.on('checkout-failed', (data: { errorReason: string }) => {
      console.error('Payment failed:', data.errorReason);
      this.handleFailure(data.errorReason);
    });

    // Checkout expired
    this.widget.on('checkout-expired', () => {
      console.log('Checkout expired');
      this.handleExpired();
    });

    // Payment processing
    this.widget.on('payment-processing', (isProcessing: boolean) => {
      this.setLoadingState(isProcessing);
    });

    // Checkout processing
    this.widget.on('checkout-processing', () => {
      this.setProcessingState(true);
    });
  }

  submitPayment() {
    try {
      this.widget?.submitPayment();
    } catch (error) {
      console.error('Error submitting payment:', error);
    }
  }

  private handleSuccess() {
    // Handle successful payment
  }

  private handleFailure(reason: string) {
    // Handle payment failure
  }

  private handleExpired() {
    // Handle expired checkout
  }

  private setLoadingState(isLoading: boolean) {
    // Update UI loading state
  }

  private setProcessingState(isProcessing: boolean) {
    // Update UI processing state
  }

  private getTheme() {
    return {
      colors: {
        seed: {
          brand: '#f44336',
          neutral: '#1f62e6',
          info: '#1c57cc',
          positive: '#e9f1ff',
          negative: '#a7c5ff',
          warning: '#6599ff'
        }
      }
    };
  }
}

Subscription Widget

The subscription widget handles recurring payments and subscription management.

Basic Usage

import { PaymentLabs, PaymentWidget, SubscriptionOffer, SubscriptionPlan } from '@paymentlabs/paymentlabs-js';

class SubscriptionManager {
  private widget: PaymentWidget | null = null;
  private subscriptionOfferData: SubscriptionOffer | null = null;
  private subscriptionPlanData: SubscriptionPlan | null = null;

  async initializeSubscription(subscriptionOfferId: string, containerId: string) {
    const sdk = PaymentLabs.create({
      environment: 'sandbox',
      merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
    });

    this.widget = await sdk.createWidget('subscription', subscriptionOfferId, {
      containerId,
      theme: this.getTheme()
    });

    this.setupEventListeners();
  }

  private setupEventListeners() {
    if (!this.widget) return;

    // Subscription offer loaded
    this.widget.on('subscription-loaded', () => {
      const subscriptionOffer = this.widget!.getSubscriptionOfferData();
      if (subscriptionOffer) {
        this.subscriptionOfferData = subscriptionOffer;
        console.log('Subscription loaded:', subscriptionOffer);
      }
    });

    // Subscription completed
    this.widget.on('subscription-completed', () => {
      console.log('Subscription completed');
      this.handleSuccess();
    });

    // Subscription plan changed
    this.widget.on('subscription-plan-changed', (plan: SubscriptionPlan) => {
      this.subscriptionPlanData = plan;
      console.log('Plan changed:', plan);
      this.updatePlanDisplay(plan);
    });

    // Subscription failed
    this.widget.on('subscription-failed', (data: { errorReason: string }) => {
      console.error('Subscription failed:', data.errorReason);
      this.handleFailure(data.errorReason);
    });

    // Subscription expired
    this.widget.on('subscription-expired', () => {
      const subscriptionOffer = this.widget!.getSubscriptionOfferData();
      if (subscriptionOffer) {
        this.subscriptionOfferData = subscriptionOffer;
      }
      console.log('Subscription expired');
      this.handleExpired();
    });

    // Payment processing
    this.widget.on('payment-processing', (isProcessing: boolean) => {
      this.setLoadingState(isProcessing);
    });

    // Checkout processing
    this.widget.on('checkout-processing', () => {
      this.setProcessingState(true);
    });
  }

  submitPayment() {
    try {
      this.widget?.submitPayment();
    } catch (error) {
      console.error('Error submitting payment:', error);
    }
  }

  private handleSuccess() {
    // Handle successful subscription
  }

  private handleFailure(reason: string) {
    // Handle subscription failure
  }

  private handleExpired() {
    // Handle expired subscription offer
  }

  private updatePlanDisplay(plan: SubscriptionPlan) {
    // Update UI with new plan details
  }

  private setLoadingState(isLoading: boolean) {
    // Update UI loading state
  }

  private setProcessingState(isProcessing: boolean) {
    // Update UI processing state
  }

  private getTheme() {
    return {
      colors: {
        seed: {
          brand: '#f44336',
          neutral: '#1f62e6',
          info: '#1c57cc',
          positive: '#e9f1ff',
          negative: '#a7c5ff',
          warning: '#6599ff'
        }
      }
    };
  }
}

Event Handling

The SDK provides a comprehensive event system for tracking widget state and user interactions.

Available Events

Checkout Events

  • checkout-loaded: Fired when the checkout widget is fully loaded
  • checkout-completed: Fired when payment is successfully completed
  • checkout-failed: Fired when payment fails
  • checkout-expired: Fired when the checkout session expires
  • checkout-processing: Fired when checkout processing begins
  • payment-processing: Fired when payment processing state changes

Subscription Events

  • subscription-loaded: Fired when the subscription widget is fully loaded
  • subscription-completed: Fired when subscription is successfully created
  • subscription-failed: Fired when subscription creation fails
  • subscription-expired: Fired when the subscription offer expires
  • subscription-plan-changed: Fired when user selects a different subscription plan
  • payment-processing: Fired when payment processing state changes
  • checkout-processing: Fired when checkout processing begins

Event Handler Example

function setupEventHandlers(widget: PaymentWidget) {
  // Generic event handler
  const handleEvent = (eventName: string, data?: any) => {
    console.log(`Event: ${eventName}`, data);
    
    // Update analytics
    analytics.track(eventName, data);
    
    // Update UI state
    updateUIState(eventName, data);
  };

  // Register all events
  const events = [
    'checkout-loaded',
    'checkout-completed', 
    'checkout-failed',
    'checkout-expired',
    'checkout-processing',
    'payment-processing'
  ];

  events.forEach(event => {
    widget.on(event, (data) => handleEvent(event, data));
  });
}

Theming

Customize the widget appearance using the theme configuration.

Theme Configuration

interface ThemeConfig {
  colors: {
    seed: {
      brand: string;      // Primary brand color (hex)
      neutral: string;    // Neutral color (hex)
      info: string;       // Info color (hex)
      positive: string;   // Success color (hex)
      negative: string;   // Error color (hex)
      warning: string;    // Warning color (hex)
    };
  };
}

Theme Example

const theme: ThemeConfig = {
  colors: {
    seed: {
      brand: '#f44336',      // Red
      neutral: '#1f62e6',    // Blue
      info: '#1c57cc',       // Dark blue
      positive: '#e9f1ff',   // Light blue
      negative: '#a7c5ff',   // Light red
      warning: '#6599ff'     // Orange
    }
  }
};

const widget = await sdk.createWidget('checkout', checkoutId, {
  containerId: 'checkout-container',
  theme
});

Examples

CDN Example (Script Tag)

<!DOCTYPE html>
<html>
<head>
    <title>Payment Labs CDN Example</title>
</head>
<body>
    <div id="checkout-container"></div>
    <button id="pay-button" style="display: none;">Pay Now</button>
    <div id="status"></div>

    <!-- Load PaymentLabs SDK from CDN -->
    <script src="https://sdk.paymentlabs.io/sdk/latest/paymentlabs.js"></script>
    <script>
        class CDNCheckoutApp {
            constructor() {
                this.widget = null;
                this.checkoutData = null;
                this.statusDiv = document.getElementById('status');
                this.init();
            }

            async init() {
                try {
                    // Payment Labs is available globally from CDN
                    const sdk = PaymentLabs.create({
                        environment: 'production', // or 'sandbox' for testing
                        merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
                    });

                    this.updateStatus('Loading checkout...');

                    this.widget = await sdk.createWidget('checkout', 'your-checkout-id', {
                        containerId: 'checkout-container',
                        theme: {
                            colors: {
                                seed: {
                                    brand: '#f44336',
                                    neutral: '#1f62e6',
                                    info: '#1c57cc',
                                    positive: '#e9f1ff',
                                    negative: '#a7c5ff',
                                    warning: '#6599ff'
                                }
                            }
                        }
                    });

                    this.setupEvents();
                } catch (error) {
                    this.updateStatus(`Initialization failed: ${error.message}`, 'error');
                }
            }

            setupEvents() {
                this.widget.on('checkout-loaded', () => {
                    this.checkoutData = this.widget.getCheckoutData();
                    this.updateStatus(`Checkout loaded: ${this.checkoutData.amount.currency} ${this.checkoutData.amount.value / 100}`);
                    document.getElementById('pay-button').style.display = 'block';
                });

                this.widget.on('checkout-completed', () => {
                    this.updateStatus('Payment completed successfully!', 'success');
                    document.getElementById('pay-button').style.display = 'none';
                });

                this.widget.on('checkout-failed', (data) => {
                    this.updateStatus(`Payment failed: ${data.errorReason}`, 'error');
                });

                this.widget.on('checkout-expired', () => {
                    this.updateStatus('Checkout session expired. Please refresh to try again.', 'warning');
                });

                this.widget.on('payment-processing', (isProcessing) => {
                    const button = document.getElementById('pay-button');
                    button.disabled = isProcessing;
                    button.textContent = isProcessing ? 'Processing...' : 'Pay Now';
                    
                    if (isProcessing) {
                        this.updateStatus('Processing payment...', 'info');
                    }
                });
            }

            submitPayment() {
                try {
                    this.widget.submitPayment();
                } catch (error) {
                    this.updateStatus(`Payment submission failed: ${error.message}`, 'error');
                }
            }

            updateStatus(message, type = 'info') {
                this.statusDiv.textContent = message;
                this.statusDiv.className = `status-${type}`;
            }
        }

        // Initialize the app when DOM is ready
        document.addEventListener('DOMContentLoaded', () => {
            const app = new CDNCheckoutApp();
            
            document.getElementById('pay-button').addEventListener('click', () => {
                app.submitPayment();
            });
        });
    </script>

    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
        }

        #checkout-container {
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 20px;
            margin: 20px 0;
            min-height: 200px;
        }

        #pay-button {
            background-color: #f44336;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
            margin: 10px 0;
        }

        #pay-button:hover:not(:disabled) {
            background-color: #d32f2f;
        }

        #pay-button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }

        #status {
            padding: 10px;
            border-radius: 4px;
            margin: 10px 0;
        }

        .status-info {
            background-color: #e3f2fd;
            color: #1976d2;
            border: 1px solid #bbdefb;
        }

        .status-success {
            background-color: #e8f5e8;
            color: #2e7d32;
            border: 1px solid #c8e6c9;
        }

        .status-error {
            background-color: #ffebee;
            color: #c62828;
            border: 1px solid #ffcdd2;
        }

        .status-warning {
            background-color: #fff3e0;
            color: #ef6c00;
            border: 1px solid #ffcc02;
        }
    </style>
</body>
</html>

Vanilla JavaScript Example (Module Import)

<!DOCTYPE html>
<html>
<head>
    <title>Payment Labs Checkout</title>
</head>
<body>
    <div id="checkout-container"></div>
    <button id="pay-button" style="display: none;">Pay Now</button>

    <script type="module">
        import { PaymentLabs } from '@paymentlabs/paymentlabs-js';

        class CheckoutApp {
            constructor() {
                this.widget = null;
                this.checkoutData = null;
                this.init();
            }

            async init() {
                const sdk = PaymentLabs.create({
                    environment: 'sandbox',
                    merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
                });

                this.widget = await sdk.createWidget('checkout', 'your-checkout-id', {
                    containerId: 'checkout-container'
                });

                this.setupEvents();
            }

            setupEvents() {
                this.widget.on('checkout-loaded', () => {
                    this.checkoutData = this.widget.getCheckoutData();
                    document.getElementById('pay-button').style.display = 'block';
                });

                this.widget.on('checkout-completed', () => {
                    alert('Payment completed successfully!');
                });

                this.widget.on('checkout-failed', (data) => {
                    alert(`Payment failed: ${data.errorReason}`);
                });
            }

            submitPayment() {
                this.widget.submitPayment();
            }
        }

        const app = new CheckoutApp();
        document.getElementById('pay-button').addEventListener('click', () => {
            app.submitPayment();
        });
    </script>
</body>
</html>

React Example

import React, { useEffect, useRef, useState } from 'react';
import { PaymentLabs, PaymentWidget } from '@paymentlabs/paymentlabs-js';

interface CheckoutComponentProps {
  checkoutId: string;
  onSuccess: () => void;
  onFailure: (reason: string) => void;
}

export function CheckoutComponent({ checkoutId, onSuccess, onFailure }: CheckoutComponentProps) {
  const [isLoading, setIsLoading] = useState(false);
  const [isReady, setIsReady] = useState(false);
  const widgetRef = useRef<PaymentWidget | null>(null);

  useEffect(() => {
    const initializeWidget = async () => {
      const sdk = PaymentLabs.create({
        environment: 'sandbox',
        merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
      });

      const widget = await sdk.createWidget('checkout', checkoutId, {
        containerId: 'checkout-container'
      });

      widgetRef.current = widget;

      widget.on('checkout-loaded', () => {
        setIsReady(true);
      });

      widget.on('checkout-completed', () => {
        onSuccess();
      });

      widget.on('checkout-failed', (data) => {
        onFailure(data.errorReason);
      });

      widget.on('payment-processing', (isProcessing) => {
        setIsLoading(isProcessing);
      });
    };

    initializeWidget();
  }, [checkoutId, onSuccess, onFailure]);

  const handleSubmit = () => {
    widgetRef.current?.submitPayment();
  };

  return (
    <div>
      <div id="checkout-container" />
      {isReady && (
        <button 
          onClick={handleSubmit} 
          disabled={isLoading}
        >
          {isLoading ? 'Processing...' : 'Pay Now'}
        </button>
      )}
    </div>
  );
}

Vue.js Example

<template>
  <div>
    <div id="checkout-container"></div>
    <button 
      v-if="isReady" 
      @click="submitPayment" 
      :disabled="isLoading"
    >
      {{ isLoading ? 'Processing...' : 'Pay Now' }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { PaymentLabs, PaymentWidget } from '@paymentlabs/paymentlabs-js';

interface Props {
  checkoutId: string;
}

interface Emits {
  (e: 'success'): void;
  (e: 'failure', reason: string): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const isLoading = ref(false);
const isReady = ref(false);
const widget = ref<PaymentWidget | null>(null);

onMounted(async () => {
  const sdk = PaymentLabs.create({
    environment: 'sandbox',
    merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
  });

  widget.value = await sdk.createWidget('checkout', props.checkoutId, {
    containerId: 'checkout-container'
  });

  widget.value.on('checkout-loaded', () => {
    isReady.value = true;
  });

  widget.value.on('checkout-completed', () => {
    emit('success');
  });

  widget.value.on('checkout-failed', (data) => {
    emit('failure', data.errorReason);
  });

  widget.value.on('payment-processing', (isProcessing) => {
    isLoading.value = isProcessing;
  });
});

const submitPayment = () => {
  widget.value?.submitPayment();
};
</script>

TypeScript Support

The SDK is written in TypeScript and provides full type definitions.

Type Definitions

// Main SDK types
import { 
  PaymentLabs, 
  PaymentWidget, 
  SubscriptionOffer, 
  SubscriptionPlan 
} from '@paymentlabs/paymentlabs-js';

// Theme configuration
import { ThemeConfig } from '@paymentlabs/paymentlabs-js/dist/types/theme';

// External model types (available through the SDK)
import { 
  Checkout, 
  CheckoutErrorReason 
} from '@paymentlabs/paymentlabs-js';

// Environment types
type SDKEnvironment = 'sandbox' | 'production';

// Widget types
type WidgetType = 'checkout' | 'subscription';

Type-Safe Event Handling

interface CheckoutEvents {
  'checkout-loaded': () => void;
  'checkout-completed': () => void;
  'checkout-failed': (data: { errorReason: CheckoutErrorReason }) => void;
  'checkout-expired': () => void;
  'checkout-processing': () => void;
  'payment-processing': (isProcessing: boolean) => void;
}

interface SubscriptionEvents {
  'subscription-loaded': () => void;
  'subscription-completed': () => void;
  'subscription-failed': (data: { errorReason: CheckoutErrorReason }) => void;
  'subscription-expired': () => void;
  'subscription-plan-changed': (plan: SubscriptionPlan) => void;
  'payment-processing': (isProcessing: boolean) => void;
  'checkout-processing': () => void;
}

// Type-safe event registration
function setupTypedEvents(widget: PaymentWidget) {
  widget.on('checkout-loaded', () => {
    // TypeScript knows this is a checkout-loaded event
  });

  widget.on('subscription-plan-changed', (plan) => {
    // TypeScript knows plan is SubscriptionPlan
    console.log(plan.name, plan.amount);
  });
}

Error Handling

The SDK provides comprehensive error handling for various scenarios.

Common Error Scenarios

class PaymentErrorHandler {
  handleWidgetError(widget: PaymentWidget) {
    widget.on('checkout-failed', (data) => {
      switch (data.errorReason) {
        case 'MISSING_INSTRUMENT':
          this.showMissingInstrumentError();
          break;
        case 'INVALID_INSTRUMENT':
          this.showInvalidInstrumentError();
          break;
        case 'CHARGE_INSTRUMENT_ERROR':
          this.showChargeError();
          break;
        case 'APPLICATION_ERROR':
          this.showApplicationError();
          break;
        case 'UNEXPECTED_ERROR':
          this.showUnexpectedError();
          break;
        default:
          this.showGenericError(data.errorReason);
      }
    });

    widget.on('checkout-expired', () => {
      this.showExpiredSessionError();
    });
  }

  private showMissingInstrumentError() {
    // Handle missing payment instrument
  }

  private showInvalidInstrumentError() {
    // Handle invalid payment instrument
  }

  private showChargeError() {
    // Handle charge error
  }

  private showApplicationError() {
    // Handle application error
  }

  private showUnexpectedError() {
    // Handle unexpected error
  }

  private showGenericError(reason: string) {
    // Handle generic errors
  }

  private showExpiredSessionError() {
    // Handle expired session
  }
}

Data Types

The SDK uses well-defined TypeScript interfaces for all data objects. Here are the key types you'll work with:

Checkout

Represents checkout/payment data. This is the main type returned by getCheckoutData().

interface Checkout {
  id: string;                    // Unique checkout identifier
  buyerEmail: string;            // Buyer's email address
  merchantId: string;            // Unique merchant identifier
  merchantName: string;          // Name of the merchant
  buyerId: string;               // Unique buyer identifier
  alias: string;                 // Description/reference
  amount: Money;                 // Amount to be charged
  status: CheckoutStatus;        // Current checkout status
  errorReason?: CheckoutErrorReason; // Error reason if status is failed
  enabledCheckoutInstruments: InstrumentType[]; // Available payment method types
  requiresFraudSession: boolean; // Whether fraud session is required
  expiresAt: string;            // ISO 8601 expiration timestamp
  links: {
    embeddable: string;         // Embeddable checkout URL
    checkout: string;           // Full checkout URL
  };
  redirectLinks?: RedirectLinks; // Optional redirect URLs
  termsOfServiceUrl: string;    // Terms of service URL
  privacyPolicyUrl: string;     // Privacy policy URL
}

type CheckoutStatus = 
  | 'PENDING'      // Awaiting payment
  | 'EXPIRED'      // Checkout expired
  | 'PROCESSING'   // Payment in progress
  | 'PAID'         // Payment successful
  | 'FAILED'       // Payment failed
  | 'UNKNOWN'      // Unknown status
  | 'CREATING'     // Checkout being created
  | 'ACTIVE'       // Checkout is active
  | 'TRIAL';       // Trial period

type CheckoutErrorReason = 
  | 'MISSING_INSTRUMENT'         // No payment instrument provided
  | 'INVALID_INSTRUMENT'         // Invalid payment instrument
  | 'CHARGE_INSTRUMENT_ERROR'    // Error charging the instrument
  | 'APPLICATION_ERROR'          // Application-level error
  | 'UNEXPECTED_ERROR';          // Unexpected system error

SubscriptionOffer

Represents subscription offer data.

interface SubscriptionOffer {
  id: string;                     // Unique offer identifier
  merchantId: string;             // Merchant identifier
  userId: string;                 // User identifier
  subscriptionPlanIds: string[];  // Array of plan IDs in this offer
  subscriptionPlans: SubscriptionPlan[]; // Available subscription plans
  expiresAt: string;              // ISO 8601 expiration timestamp
  links: {
    embeddable: string;           // Embeddable URL for the offer
    self: string;                 // Direct link to the offer
  };
}

SubscriptionPlan

Represents individual subscription plan within an offer.

interface SubscriptionPlan {
  id: string;                    // Unique plan identifier
  merchantId: string;            // Merchant identifier
  merchantDisplayName: string;   // Merchant display name
  sort: string;                  // Sorting information for ordering/grouping
  name: string;                  // Plan display name
  description?: string;          // Plan description
  enabledCheckoutInstruments: InstrumentType[]; // Available payment methods
  schedule: Schedule;            // Billing schedule
  amount: Money;                 // Amount to be charged
  trialPeriod?: Schedule;        // Optional trial period
  status: SubscriptionPlanStatus; // Plan status
  termsOfServiceUrl?: string;    // Terms of service URL
  privacyPolicyUrl?: string;     // Privacy policy URL
}

interface Schedule {
  interval: ScheduleInterval;    // Billing interval
  intervalCount: number;         // Number of intervals between billings
}

type ScheduleInterval = 
  | 'daily'      // Daily billing
  | 'weekly'     // Weekly billing  
  | 'monthly'    // Monthly billing
  | 'yearly';    // Annual billing

type SubscriptionPlanStatus = 
  | 'ACTIVE'     // Plan is active and can be subscribed to
  | 'CANCELED';  // Plan cannot be subscribed to

interface Money {
  value: number;                 // Amount in float point value
  currency: string;              // ISO 4217 currency code
  formattedValue: string;        // Formatted value with currency
  digits: number;                // Decimal digits for the currency
  wholeValue: number;            // Value without decimal points (e.g., cents)
}

Subscription

Represents an active subscription.

interface Subscription {
  id: string;                    // Unique subscription identifier
  merchantId: string;            // Merchant identifier
  userId: string;                // User identifier
  subscriptionPlanId: string;    // Plan identifier
  subscriptionPlanName: string;  // Plan name
  subscribeDate: string;         // ISO 8601 subscription date
  checkoutInstrument: InstrumentResponse; // Payment instrument
  instrumentType: InstrumentType; // Instrument type
  schedule: Schedule;            // Billing schedule
  amount: Money;                 // Amount to be charged
  status: CheckoutStatus;        // Subscription status
  nextPaymentDate?: string;      // Next payment date
  termsOfServiceUrl?: string;    // Terms of service URL
  privacyPolicyUrl?: string;     // Privacy policy URL
}

CheckoutErrorReason

Defines possible checkout error reasons.

type CheckoutErrorReason = 
  | 'MISSING_INSTRUMENT'         // No payment instrument provided
  | 'INVALID_INSTRUMENT'         // Invalid payment instrument
  | 'CHARGE_INSTRUMENT_ERROR'    // Error charging the instrument
  | 'APPLICATION_ERROR'          // Application-level error
  | 'UNEXPECTED_ERROR';          // Unexpected system error

ThemeConfig

Defines widget appearance customization.

interface ThemeConfig {
  colors: {
    seed: {
      brand: string;             // Primary brand color (hex)
      neutral: string;           // Neutral/secondary color (hex) 
      info: string;              // Information color (hex)
      positive: string;          // Success/positive color (hex)
      negative: string;          // Error/negative color (hex)
      warning: string;           // Warning color (hex)
    };
  };
}

WidgetOptions

Defines the configuration options for creating payment widgets. This is the main options object passed to sdk.createWidget().

interface WidgetOptions {
  containerId: string;           // ID of the HTML element where the widget will be rendered
  theme?: ThemeConfig;           // Optional theme configuration for widget appearance
}

Properties:

  • containerId (string, required): The ID of the HTML element where the payment widget will be rendered. This element must exist in the DOM before creating the widget.
  • theme (ThemeConfig, optional): Custom theme configuration to customize the widget's appearance. If not provided, the widget will use default styling.

Usage Example:

const widget = await sdk.createWidget('checkout', checkoutId, {
  containerId: 'payment-container',
  theme: {
    colors: {
      seed: {
        brand: '#f44336',
        neutral: '#1f62e6',
        info: '#1c57cc',
        positive: '#e9f1ff',
        negative: '#a7c5ff',
        warning: '#6599ff'
      }
    }
  }
});

Event Data Types

Event handlers receive typed data based on the event:

// Checkout Events
interface CheckoutFailedEvent {
  errorReason: CheckoutErrorReason;
}

// Subscription Events  
interface SubscriptionFailedEvent {
  errorReason: CheckoutErrorReason;
}

interface SubscriptionPlanChangedEvent {
  plan: SubscriptionPlan;
}

// Payment Processing Events
type PaymentProcessingEvent = boolean; // true = processing, false = idle

Usage Example with Types

import { 
  PaymentLabs, 
  PaymentWidget,
  Checkout,
  SubscriptionOffer,
  SubscriptionPlan,
  CheckoutErrorReason 
} from '@paymentlabs/paymentlabs-js';

class TypedPaymentHandler {
  private widget: PaymentWidget | null = null;

  async initializeCheckout(checkoutId: string) {
    const sdk = PaymentLabs.create({
      environment: 'sandbox',
      merchantId: 'C-220c070ec91944b8ad39814ec43d9c05'
    });

    this.widget = await sdk.createWidget('checkout', checkoutId, {
      containerId: 'checkout-container'
    });

    // Type-safe event handling
    this.widget.on('checkout-loaded', () => {
      const checkoutData: Checkout | null = this.widget!.getCheckoutData();
      if (checkoutData) {
        console.log(`Checkout for ${checkoutData.amount.currency} ${checkoutData.amount.value / 100}`);
      }
    });

    this.widget.on('checkout-failed', (event: { errorReason: CheckoutErrorReason }) => {
      this.handlePaymentError(event.errorReason);
    });

    this.widget.on('subscription-plan-changed', (plan: SubscriptionPlan) => {
      console.log(`Plan changed to: ${plan.name} - ${plan.amount.currency} ${plan.amount.value / 100}/${plan.schedule.interval}`);
    });
  }

  private handlePaymentError(reason: CheckoutErrorReason) {
    switch (reason) {
      case 'MISSING_INSTRUMENT':
        console.error('No payment method was provided. Please select a payment method.');
        break;
      case 'INVALID_INSTRUMENT':
        console.error('The payment method is invalid. Please check your payment details.');
        break;
      case 'CHARGE_INSTRUMENT_ERROR':
        console.error('There was an error processing your payment. Please try again.');
        break;
      case 'APPLICATION_ERROR':
        console.error('An application error occurred. Please contact support.');
        break;
      case 'UNEXPECTED_ERROR':
        console.error('An unexpected error occurred. Please try again later.');
        break;
      default:
        console.error(`Payment failed: ${reason}`);
    }
  }
}

Best Practices

  1. Environment Configuration: Always use the appropriate environment for your use case
  2. Error Handling: Implement comprehensive error handling for all events
  3. Loading States: Use the payment-processing and checkout-processing events to show appropriate loading states
  4. Theme Consistency: Ensure your theme colors match your brand guidelines
  5. Event Cleanup: Remove event listeners when components unmount to prevent memory leaks
  6. Type Safety: Use TypeScript for better development experience and error prevention

Support

For technical support and questions, please refer to the official Payment Labs documentation or contact our support team.