npm package: @payos-inc/payos-js
Description: PayOS JavaScript SDK for browser-based checkout and wallet onboarding
Author: PayOS Inc.
Latest version: 1.0.8

# @payos-inc/payos-js

JavaScript SDK for PayOS integration, providing checkout and wallet onboarding functionality.

## Installation

```bash
npm install @payos-inc/payos-js
```

CDN:
```html
<script src="https://unpkg.com/@payos-inc/payos-js@latest/dist/payos-js.min.js"></script>
```

## Quick Start

```javascript
import { PayOS } from '@payos-inc/payos-js';

const payos = new PayOS();

// Wallet Onboarding - Add payment methods
payos.walletOnboard.open({
  token: 'YOUR_TOKEN',      // Get from your backend
  mode: 'popup',            // or 'redirect'
  environment: 'sandbox',   // or 'production'
  onComplete: (result) => {
    console.log('Wallet User ID:', result.walletUserId);
    console.log('Cards added:', result.linkedCardIds);
  },
  onError: (error) => {
    console.error('Error:', error);
  }
});

// Checkout - Process payments
payos.checkout.open({
  token: 'YOUR_TOKEN',      // Get from your backend
  mode: 'popup',            // or 'redirect'
  environment: 'sandbox',   // or 'production'
  onComplete: (result) => {
    console.log('Payment Intent ID:', result.paymentIntentId);
    console.log('Auth completed:', result.authCompleted);
    console.log('Credentials generated:', result.credentialsGenerated);
  },
  onError: (error) => {
    console.error('Error:', error);
  }
});

// Card Element - Embed secure card capture
const cardElement = PayOS.cardElement.init('#card-container', {
  token: 'YOUR_TOKEN',
  environment: 'sandbox',
  styles: {
    layout: { preset: 'two-row' },
    base: { fontSize: '16px' }
  },
  onTokenized: (result) => {
    console.log('Card tokenized:', result.card);
  }
});
```

## API Reference

### Constructor

```javascript
const payos = new PayOS();
```

### Methods
#### `payos.walletOnboard.open(options)`
Opens the wallet onboarding flow to add payment methods.

#### `payos.walletOnboard.close()`
Closes the current wallet onboarding session.

#### `payos.walletOnboard.isActive()`
Returns `true` if wallet onboarding is currently active.

#### `payos.checkout.open(options)`
Opens the checkout flow to process payments.

#### `payos.checkout.close()`
Closes the current checkout session.

#### `payos.checkout.isActive()`
Returns `true` if checkout is currently active.

#### `payos.cardElement.init(container, options)`
Creates and mounts a secure card capture element in the specified container.

**Returns:** `CardElementInstance` with methods:
- `mount()` - Mount the element to DOM
- `unmount()` - Remove from DOM
- `destroy()` - Clean up completely
- `submit()` - Trigger form submission
- `configure(config)` - Update configuration
- `updateStyles(styles)` - Update styles dynamically

## Options

### Wallet Onboarding Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `token` | string | Yes | Session token from your backend |
| `mode` | string | No | 'popup' or 'redirect' (default: 'popup') |
| `environment` | string | No | 'sandbox' or 'production' (default: 'sandbox') |
| `merchantName` | string | No | Your merchant name to display |
| `returnUrl` | string | No | URL for redirect mode |
| `onComplete` | function | No | Called with `{walletUserId, linkedCardIds}` |
| `onError` | function | No | Called on error |
| `onCancel` | function | No | Called when user cancels |
| `onReady` | function | No | Called when UI is ready |

### Checkout Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `token` | string | Yes | Checkout token from your backend |
| `mode` | string | No | 'popup' or 'redirect' (default: 'popup') |
| `environment` | string | No | 'sandbox' or 'production' (default: 'sandbox') |
| `returnUrl` | string | No | URL for redirect mode |
| `onComplete` | function | No | Called when checkout completes |
| `onError` | function | No | Called on error |
| `onCancel` | function | No | Called when user cancels |
| `onReady` | function | No | Called when UI is ready |

### Card Element Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `token` | string | Yes | Session token from your backend |
| `environment` | string | No | 'sandbox' or 'production' (default: 'sandbox') |
| `styles` | object | No | Custom styles for the card fields (see Styling section) |
| `height` | string | No | Container height (default: '720px') |
| `onReady` | function | No | Called when card element is ready |
| `onTokenized` | function | No | Called with tokenized card data |
| `onChange` | function | No | Called when field validation changes |
| `onError` | function | No | Called on error |
| `onCancel` | function | No | Called when user cancels |

## Display Modes

### Popup Mode (Default)
Opens in a new window:
```javascript
payos.walletOnboard.open({
  token: 'token',
  mode: 'popup',
  environment: 'sandbox'
});
```

### Redirect Mode
Redirects the current page:
```javascript
payos.walletOnboard.open({
  token: 'token',
  mode: 'redirect',
  environment: 'sandbox',
  returnUrl: 'https://yourapp.com/complete'
});
```

### Handling Redirect Returns
PayOS returns parameters prefixed with `payos_`:
```javascript
// In your return URL handler
const params = new URLSearchParams(window.location.search);
const status = params.get('payos_status'); // 'success', 'cancel', or 'error'
const encodedData = params.get('payos_data'); // URL-encoded JSON
const timestamp = params.get('payos_timestamp');

if (status === 'success' && encodedData) {
  // Decode the data
  const data = JSON.parse(decodeURIComponent(encodedData));
  // For wallet onboarding:
  if (data.walletUserId) {
    console.log('Wallet User ID:', data.walletUserId);
    console.log('Cards Added:', data.linkedCardIds);
  }
  
  // For checkout:
  if (data.paymentIntentId) {
    console.log('Payment Intent ID:', data.paymentIntentId);
    console.log('Auth Completed:', data.authCompleted);
  }
}
```

## Card Element

### Embedding Secure Card Capture

The Card Element provides a secure, PCI-compliant way to capture card details directly in your application. Card data never touches your servers - it's tokenized through secure iframes.

```javascript
// Basic setup
const cardElement = PayOS.cardElement.init('#card-container', {
  token: 'YOUR_TOKEN',
  environment: 'sandbox',
  onTokenized: (result) => {
    console.log('Card tokenized:', result.card);
    // Submit to your backend
  }
});

// With custom styling
const cardElement = PayOS.cardElement.init('#card-container', {
  token: 'YOUR_TOKEN',
  environment: 'sandbox',
  styles: {
    layout: {
      preset: 'two-row',        // Layout preset
      showLabels: true,         // Show field labels
      gap: '16px'              // Space between fields
    },
    base: {
      fontSize: '16px',
      color: '#333',
      padding: '12px',
      borderRadius: '8px',
      border: '1px solid #ddd',
      '&:focus': {
        borderColor: '#007bff',
        outline: 'none'
      }
    },
    cardNumber: {
      paddingRight: '50px'     // Space for card icon
    }
  },
  onTokenized: (result) => {
    console.log('Card:', result.card);
    console.log('Billing:', result.billingAddress);
  }
});
```

### Card Element Styling

The Card Element supports extensive styling through the `styles` option:

#### Layout Styles
Controls the overall form layout:

```javascript
styles: {
  layout: {
    preset: 'two-row',          // 'single-row' | 'two-row' | 'compact' | 'stacked'
    showLabels: true,           // Show/hide field labels
    showErrorMessages: true,    // Show validation errors
    gap: '16px',               // Space between fields
    padding: '20px',           // Container padding
    backgroundColor: '#f5f5f5', // Background color
    labels: {
      cardNumber: 'Card Number',  // Custom label text
      expiry: 'Expiration',        // or false to hide
      cvv: 'Security Code'
    }
  }
}
```

**Layout Presets:**
- `two-row` (default): Card number on first row, expiry/CVV on second
- `single-row`: All fields in one horizontal row
- `compact`: Minimal spacing for tight layouts
- `stacked`: All fields vertically stacked (mobile-friendly)

#### Field Styles
Customize individual field appearance:

```javascript
styles: {
  // Base styles apply to all fields
  base: {
    fontSize: '16px',
    fontFamily: 'system-ui, sans-serif',
    color: '#333',
    padding: '12px',
    height: '48px',
    border: '1px solid #e0e0e0',
    borderRadius: '8px',
    backgroundColor: 'white',
    '&:focus': {
      borderColor: '#007bff',
      boxShadow: '0 0 0 3px rgba(0,123,255,0.1)'
    },
    '&::placeholder': {
      color: '#999'
    }
  },

  // Override for specific fields
  cardNumber: {
    fontSize: '18px'
  },
  expiry: {
    width: '140px'
  },
  cvc: {
    width: '100px'
  }
}
```

### Card Element Events

Handle card element events for better UX:

```javascript
const cardElement = PayOS.cardElement.init('#card-container', {
  token: 'YOUR_TOKEN',

  onReady: () => {
    console.log('Card element ready');
    // Enable submit button
  },

  onChange: (event) => {
    console.log('Validation:', {
      complete: event.complete,  // All fields valid
      empty: event.empty,        // All fields empty
      error: event.error         // Validation error message
    });
    // Update UI based on validation
  },

  onTokenized: (result) => {
    console.log('Success:', {
      card: result.card,              // Card details
      billingAddress: result.billingAddress,
      walletUserId: result.walletUserId
    });
    // Submit to your backend
  },

  onError: (error) => {
    console.error('Error:', error);
    // Show error to user
  }
});
```

### Programmatic Control

Control the card element after initialization:

```javascript
// Submit the form programmatically
document.getElementById('submit-btn').onclick = () => {
  cardElement.submit();
};

// Configure with user data
cardElement.configure({
  walletUserId: 'user_123',
  cardholderName: 'John Doe',
  billingAddress: {
    line1: '123 Main St',
    city: 'San Francisco',
    state: 'CA',
    postalCode: '94105',
    country: 'US'
  }
});

// Update styles dynamically
cardElement.updateStyles({
  base: {
    fontSize: '18px',
    color: '#000'
  }
});

// Clean up
cardElement.unmount();  // Remove from DOM
cardElement.destroy();  // Complete cleanup
```

### Complete Example

```html
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/@payos-inc/payos-js@latest/dist/payos-js.min.js"></script>
</head>
<body>
  <div id="card-element"></div>
  <button id="pay-button">Pay</button>

  <script>
    const payos = new PayOS();

    // Initialize card element
    const cardElement = PayOS.cardElement.init('#card-element', {
      token: 'YOUR_TOKEN_HERE',
      environment: 'sandbox',
      styles: {
        layout: {
          preset: 'two-row',
          showLabels: true
        },
        base: {
          fontSize: '16px',
          padding: '12px',
          borderRadius: '8px'
        }
      },
      onChange: (event) => {
        // Enable/disable pay button based on validation
        document.getElementById('pay-button').disabled = !event.complete;
      },
      onTokenized: async (result) => {
        // Send to your backend
        const response = await fetch('/api/process-payment', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            cardId: result.card.id,
            walletUserId: result.walletUserId
          })
        });

        if (response.ok) {
          alert('Payment successful!');
        }
      },
      onError: (error) => {
        alert('Error: ' + error.message);
      }
    });

    // Handle pay button click
    document.getElementById('pay-button').onclick = () => {
      cardElement.submit();
    };
  </script>
</body>
</html>
```

## TypeScript

```typescript
import { PayOS, CardElementStyles, CardElementTokenizedEvent } from '@payos-inc/payos-js';

const payos = new PayOS();

// Wallet Onboarding with types
payos.walletOnboard.open({
  token: 'token',
  mode: 'popup',
  environment: 'sandbox',
  onComplete: (result) => {
    // TypeScript support included
    console.log(result.walletUserId); // string
    console.log(result.linkedCardIds); // string[]
  }
});

// Card Element with typed styles
const styles: CardElementStyles = {
  layout: {
    preset: 'two-row',
    showLabels: true,
    labels: {
      cardNumber: 'Card Number',
      expiry: 'Exp Date',
      cvv: 'CVV'
    }
  },
  base: {
    fontSize: '16px',
    color: '#333',
    padding: '12px',
    '&:focus': {
      borderColor: '#007bff'
    }
  }
};

const cardElement = PayOS.cardElement.init('#card-container', {
  token: 'token',
  environment: 'sandbox',
  styles,
  onTokenized: (event: CardElementTokenizedEvent) => {
    console.log(event.card?.id);          // string | undefined
    console.log(event.card?.last4);       // string | undefined
    console.log(event.walletUserId);      // string | undefined
  }
});
```

## Browser Support

- Chrome 89+
- Firefox 87+
- Safari 14+
- Edge 89+

## License

MIT

## Support

Contact: support@payos.ai  
Documentation: https://docs.payos.ai