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
- Quick Start
- API Reference
- Checkout Widget
- Subscription Widget
- Event Handling
- Billing Details Configuration
- Theming
- Examples
- TypeScript Support
- Data Types
- Error Handling
- Best Practices
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:
- Payment Labs Account
- Checkout: Create a checkout via Payment Labs API or dashboard
- 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:
- Create Subscription Plan: Create Subscription Plan
- 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)
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)
sdk.createWidget(type, id, options)
Creates a payment widget.
Parameters:
type
(string): Widget type. Options:'checkout'
,'subscription'
id
(string): Checkout ID or subscription offer IDoptions
(object):containerId
(string): ID of the HTML element where the widget will be renderedtheme
(ThemeConfig, optional): Custom theme configurationbillingDetails
(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)
widget.on(event, callback)
Registers an event listener.
Parameters:
event
(string): Event namecallback
(function): Event handler function
widget.submitPayment(request?)
widget.submitPayment(request?)
Submits the payment for processing.
Parameters:
request
(object, optional):billingDetails
(object, optional):address
(object, optional): Address information whenbillingDetails.fields.address
is set tofalse
address1
(string, required): Street addressaddress2
(string, optional): Apartment, suite, etc.city
(string, required): Citystate
(string, required): State/ProvincepostalCode
(string, required): ZIP/Postal codecountry
(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()
widget.getCheckoutData()
Retrieves the current checkout data.
Returns: Checkout | null
widget.getSubscriptionOfferData()
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 loadedcheckout-completed
: Fired when payment is successfully completedcheckout-failed
: Fired when payment failscheckout-expired
: Fired when the checkout session expirescheckout-processing
: Fired when checkout processing beginspayment-processing
: Fired when payment processing state changes
Subscription Events
subscription-loaded
: Fired when the subscription widget is fully loadedsubscription-completed
: Fired when subscription is successfully createdsubscription-failed
: Fired when subscription creation failssubscription-expired
: Fired when the subscription offer expiressubscription-plan-changed
: Fired when user selects a different subscription planpayment-processing
: Fired when payment processing state changescheckout-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)
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
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 providingbillingDetails.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
- Choose the right approach: Use SDK-rendered fields for simple cases, custom address collection for complex forms
- Validate address data: When providing custom address data, ensure all required fields are present
- Handle errors gracefully: Always implement proper error handling for payment failures
- Test both scenarios: Verify your integration works with both
address: true
andaddress: 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 EffectWhen 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 typesaddress
(boolean, optional): Controls address field renderingtrue
orundefined
: SDK renders address fields (default)false
: SDK does not render address fields, address must be provided viasubmitPayment()
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 viasubmitPayment()
.
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 processingaddress
(object, optional): Address information (required whenbillingDetails.fields.address
isfalse
)address1
(string, required): Street addressaddress2
(string, optional): Apartment, suite, etc.city
(string, required): Citystate
(string, required): State/ProvincepostalCode
(string, required): ZIP/Postal codecountry
(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
- Environment Configuration: Always use the appropriate environment for your use case
- Error Handling: Implement comprehensive error handling for all events
- Loading States: Use the
payment-processing
andcheckout-processing
events to show appropriate loading states - Theme Consistency: Ensure your theme colors match your brand guidelines
- Event Cleanup: Remove event listeners when components unmount to prevent memory leaks
- 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.
Updated 2 days ago