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 with billing details configuration
const widget = await sdk.createWidget("checkout", "your-checkout-id", {
  containerId: "checkout-container",
  theme: {
    colors: {
      seed: {
        brand: "#f44336",
        neutral: "#1f62e6",
      },
    },
  },
  // Optional: Configure billing details collection
  billingDetails: {
    fields: {
      address: true, // SDK will render address fields (default behavior)
    },
  },
});

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

// Submit payment (address fields are rendered by SDK)
widget.submitPayment();

// Alternative: Custom address collection
// const widgetWithCustomAddress = await sdk.createWidget('checkout', 'your-checkout-id', {
//   containerId: 'checkout-container',
//   billingDetails: {
//     fields: {
//       address: false  // SDK will NOT render address fields
//     }
//   }
// });
//
// // Submit with custom address
// await widgetWithCustomAddress.submitPayment({
//   billingDetails: {
//     address: {
//       address1: "123 Main Street",
//       city: "New York",
//       state: "NY",
//       postalCode: "10001",
//       country: "US"
//     }
//   }
// });

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
    • billingDetails (BillingDetailsConfig, optional): Billing details collection configuration

Returns: Promise<PaymentWidget>

Usage Examples:

// Basic widget creation (SDK renders address fields)
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
});

// Widget with custom theme
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
  theme: {
    colors: {
      seed: {
        brand: "#f44336",
        neutral: "#1f62e6",
      },
    },
  },
});

// Widget with custom billing details configuration
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
  billingDetails: {
    fields: {
      address: false, // SDK will NOT render address fields
    },
  },
});

// Complete configuration example
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
  theme: {
    colors: {
      seed: {
        brand: "#f44336",
        neutral: "#1f62e6",
      },
    },
  },
  billingDetails: {
    fields: {
      address: true, // SDK will render address fields (default)
    },
  },
});

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(request?)

Submits the payment for processing.

Parameters:

  • request (object, optional):
    • billingDetails (object, optional):
      • address (object, optional): Address information when billingDetails.fields.address is set to false
        • address1 (string, required): Street address
        • address2 (string, optional): Apartment, suite, etc.
        • city (string, required): City
        • state (string, required): State/Province
        • postalCode (string, required): ZIP/Postal code
        • country (string, required): Country code (ISO 3166-1 alpha-2)

Returns: Promise<{ success: boolean; instrumentId?: string; transactionId?: string; error?: string; }>

Usage Examples:

// Basic payment submission (SDK renders address fields)
const result = await widget.submitPayment();

// Payment submission with custom address (when address fields are disabled)
const result = await widget.submitPayment({
  billingDetails: {
    address: {
      address1: "123 Main Street",
      address2: "Apt 4B",
      city: "New York",
      state: "NY",
      postalCode: "10001",
      country: "US",
    },
  },
});
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));
  });
}

Billing Details Configuration

The SDK provides flexible options for collecting billing address information. You can choose to either let the SDK render address fields automatically, or provide address data programmatically when submitting payments.

BillingDetailsConfig

Controls how billing address information is collected and handled.

interface BillingDetailsConfig {
  fields?: {
    address?: boolean; // Controls address field rendering
  };
}

Address Field Behavior

The billingDetails.fields.address setting determines how address information is handled:

address: true (Default)

  • SDK renders address fields in the payment widget
  • Users fill out address information directly in the widget
  • Any address data provided to submitPayment() is ignored
  • This is the default behavior when billingDetails is not specified

address: false

  • SDK does NOT render address fields in the payment widget
  • Address information must be provided via submitPayment() method
  • Enforcement: If no address is provided, payment submission will fail with the error: "Address information is required but not provided"

Configuration Examples

Default Behavior (Address Fields Rendered)

// Option 1: No billingDetails specified (default)
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
});

// Option 2: Explicitly enable address fields
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
  billingDetails: {
    fields: {
      address: true, // SDK renders address fields
    },
  },
});

// Submit payment (address data is ignored)
await widget.submitPayment();

Custom Address Collection (No Address Fields Rendered)

// Configure widget to not render address fields
const widget = await sdk.createWidget("checkout", checkoutId, {
  containerId: "payment-container",
  billingDetails: {
    fields: {
      address: false, // SDK will NOT render address fields
    },
  },
});

// Submit payment with provided address
await widget.submitPayment({
  billingDetails: {
    address: {
      address1: "123 Main Street",
      address2: "Apt 4B", // Optional
      city: "New York",
      state: "NY",
      postalCode: "10001",
      country: "US",
    },
  },
});

SubmitPayment with Billing Details

When billingDetails.fields.address is set to false, you must provide address information in the submitPayment() call:

interface SubmitPaymentRequest {
  billingDetails?: {
    address?: {
      address1: string; // Required: Street address
      address2?: string; // Optional: Apartment, suite, etc.
      city: string; // Required: City
      state: string; // Required: State/Province
      postalCode: string; // Required: ZIP/Postal code
      country: string; // Required: Country code (ISO 3166-1 alpha-2)
    };
  };
}

Complete Integration Example

Here's a complete example showing both configuration approaches:

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

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

  // Method 1: Let SDK handle address collection
  async initializeWithSDKAddressCollection(checkoutId: string) {
    const sdk = PaymentLabs.create({
      environment: "sandbox",
      merchantId: "C-220c070ec91944b8ad39814ec43d9c05",
    });

    // SDK will render address fields automatically
    this.widget = await sdk.createWidget("checkout", checkoutId, {
      containerId: "checkout-container",
      // No billingDetails specified = address fields rendered by default
    });

    this.setupEventListeners();
  }

  // Method 2: Provide address programmatically
  async initializeWithCustomAddressCollection(checkoutId: string) {
    const sdk = PaymentLabs.create({
      environment: "sandbox",
      merchantId: "C-220c070ec91944b8ad39814ec43d9c05",
    });

    // SDK will NOT render address fields
    this.widget = await sdk.createWidget("checkout", checkoutId, {
      containerId: "checkout-container",
      billingDetails: {
        fields: {
          address: false, // Don't render address fields
        },
      },
    });

    this.setupEventListeners();
  }

  // Submit payment with custom address
  async submitPaymentWithAddress(addressData: {
    address1: string;
    address2?: string;
    city: string;
    state: string;
    postalCode: string;
    country: string;
  }) {
    if (!this.widget) {
      throw new Error("Widget not initialized");
    }

    try {
      const result = await this.widget.submitPayment({
        billingDetails: {
          address: addressData,
        },
      });

      if (result.success) {
        console.log("Payment successful:", result.transactionId);
      } else {
        console.error("Payment failed:", result.error);
      }
    } catch (error) {
      console.error("Payment submission error:", error);
    }
  }

  // Submit payment without address (when SDK renders fields)
  async submitPaymentWithSDKAddress() {
    if (!this.widget) {
      throw new Error("Widget not initialized");
    }

    try {
      const result = await this.widget.submitPayment();
      // Address data is collected by the SDK-rendered fields

      if (result.success) {
        console.log("Payment successful:", result.transactionId);
      } else {
        console.error("Payment failed:", result.error);
      }
    } catch (error) {
      console.error("Payment submission error:", error);
    }
  }

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

    this.widget.on("checkout-loaded", () => {
      console.log("Checkout loaded and ready");
    });

    this.widget.on("checkout-completed", () => {
      console.log("Payment completed successfully");
    });

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

// Usage examples
const paymentManager = new PaymentManager();

// Example 1: SDK handles address collection
await paymentManager.initializeWithSDKAddressCollection("your-checkout-id");
await paymentManager.submitPaymentWithSDKAddress();

// Example 2: Custom address collection
await paymentManager.initializeWithCustomAddressCollection("your-checkout-id");
await paymentManager.submitPaymentWithAddress({
  address1: "123 Main Street",
  address2: "Apt 4B",
  city: "New York",
  state: "NY",
  postalCode: "10001",
  country: "US",
});

Enforcement Logic

The SDK enforces the billing details configuration through validation in the submitPayment() method:

// SDK validation logic (internal)
const billingConfig = this.options?.billingDetails;
if (
  billingConfig?.fields?.address === false &&
  !request?.billingDetails?.address
) {
  return {
    success: false,
    error: "Address information is required but not provided",
  };
}

When this validation triggers:

  • Widget is configured with billingDetails.fields.address = false
  • submitPayment() is called without providing billingDetails.address
  • Payment submission is immediately rejected before processing

Error Handling

When billingDetails.fields.address is set to false but no address is provided in submitPayment(), the payment will fail with a clear error message:

try {
  await widget.submitPayment(); // No address provided
} catch (error) {
  // Error: "Address information is required but not provided"
  console.error("Payment failed:", error);
}

// Correct usage when address=false
await widget.submitPayment({
  billingDetails: {
    address: {
      address1: "123 Main Street",
      city: "New York",
      state: "NY",
      postalCode: "10001",
      country: "US",
    },
  },
});

Best Practices

  1. Choose the right approach: Use SDK-rendered fields for simple cases, custom address collection for complex forms
  2. Validate address data: When providing custom address data, ensure all required fields are present
  3. Handle errors gracefully: Always implement proper error handling for payment failures
  4. Test both scenarios: Verify your integration works with both address: true and address: false

Theming

Customize the widget appearance using the theme configuration.

Theme Configuration

interface ThemeConfig {
  mode: "dark" | "light"; // light is the default mode
  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,
});

function updateThemeMode(mode: "light" | "dark") {
  await sdk.updateTheme(mode);
}

⚠️

Important: Theme Switching Side Effect

When changing the themeMode, all secure input fields
(💳 Cardholder Name, Card Number, Expiration Date, Security Code)
will be re-rendered for security reasons.

This means any values entered in these fields may be cleared.


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)
    };
  };
}

BillingDetailsConfig

Defines billing details collection configuration.

interface BillingDetailsConfig {
  fields?: {
    address?: boolean; // Controls whether address fields are rendered
  };
}

Properties:

  • fields (object, optional): Configuration for individual field types
    • address (boolean, optional): Controls address field rendering
      • true or undefined: SDK renders address fields (default)
      • false: SDK does not render address fields, address must be provided via submitPayment()

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
  billingDetails?: BillingDetailsConfig; // Optional billing details configuration
}

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.
  • billingDetails (BillingDetailsConfig, optional): Configuration for billing details collection. Controls whether address fields are rendered in the widget or provided via submitPayment().

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",
      },
    },
  },
  billingDetails: {
    fields: {
      address: false, // Don't render address fields, use provided address
    },
  },
});

SubmitPaymentRequest

Defines the request object for payment submission.

interface SubmitPaymentRequest {
  billingDetails?: {
    address?: {
      address1: string; // Required: Street address
      address2?: string; // Optional: Apartment, suite, etc.
      city: string; // Required: City
      state: string; // Required: State/Province
      postalCode: string; // Required: ZIP/Postal code
      country: string; // Required: Country code (ISO 3166-1 alpha-2)
    };
  };
}

Properties:

  • billingDetails (object, optional): Billing information for payment processing
    • address (object, optional): Address information (required when billingDetails.fields.address is false)
      • address1 (string, required): Street address
      • address2 (string, optional): Apartment, suite, etc.
      • city (string, required): City
      • state (string, required): State/Province
      • postalCode (string, required): ZIP/Postal code
      • country (string, required): Country code (ISO 3166-1 alpha-2)

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.