# Embedded Checkout (v2)

The **xMoney.js v2 SDK** (`xmoney.js`) powers **Embedded Checkout**, allowing you to securely embed a payment form — or individual payment components — directly into your website. It handles input validation, secure card data collection, and payment processing without sensitive data ever touching your servers.

**Embedded Checkout** keeps customers on your site throughout the payment process, unlike Hosted Checkout which redirects to xMoney's payment page.

## Installation

Include the v2 SDK script on every page where you want to render a payment component.


```html
<!-- Production -->
<script src="https://secure.xmoney.com/sdk/v2/xmoney.js"></script>

<!-- Staging -->
<script src="https://secure-stage.xmoney.com/sdk/v2/xmoney.js"></script>
```

The SDK exposes a global `window.XMoney` object that contains all payment factories:


```javascript
window.XMoney.paymentForm(config)
window.XMoney.paymentCard(config)
window.XMoney.applePay(config)
window.XMoney.googlePay(config)
window.XMoney.savedCardPayment(config)
window.XMoney.getPaymentMethodCapabilities()
```

## About Embedded Checkout

**Embedded Checkout** is xMoney's solution for accepting payments directly on your website. Payment UI is embedded in your page using iframes, providing a seamless checkout experience without redirecting customers away from your site.

**Key Benefits:**

- Customers stay on your website throughout checkout
- Full control over checkout page design and flow
- PCI DSS compliant — card data never touches your servers
- Supports cards, Google Pay, Apple Pay, and saved cards
- Compose a single full-featured form or build a custom multi-method layout


For redirect-based checkout flows, see [Hosted Checkout](/guides/checkout/hosted-checkout).

## SDK Methods Overview

The v2 SDK is modular: each payment method has its own factory. All factories return a **Promise** that resolves to an instance with lifecycle methods.

| Method | Purpose | Has `container` |
|  --- | --- | --- |
| `XMoney.paymentForm()` | Full-featured form with card input, Google Pay, and Apple Pay | **Yes** |
| `XMoney.paymentCard()` | Standalone card input element for custom checkout layouts | **Yes** |
| `XMoney.applePay()` | Native Apple Pay button | **Yes** |
| `XMoney.googlePay()` | Native Google Pay button | **Yes** |
| `XMoney.savedCardPayment()` | Headless payment with a previously saved card | No |
| `XMoney.getPaymentMethodCapabilities()` | Detect Apple Pay / Google Pay availability on the user's device | — |


## Integration Flow

Integrating the v2 SDK into your application involves these steps:

1. **Load the SDK** — Add the `xmoney.js` v2 script tag to your HTML
2. **Create a Payment Intent** — Call your backend to obtain `orderPayload` and `orderChecksum`
3. **Initialize a payment method** — Call the appropriate `window.XMoney.*()` factory
4. **Handle callbacks** — Implement `onReady`, `onPaymentComplete`, `onPaymentProcessing`, `onError`
5. **Cleanup** — Call `instance.destroy()` when your component unmounts


## Checking Payment Method Capabilities

Before rendering Apple Pay or Google Pay buttons, check whether the browser/device supports them:


```javascript
const capabilities = await window.XMoney.getPaymentMethodCapabilities()

if (capabilities.googlePay.supported) {
  // Render Google Pay button
}

if (capabilities.applePay.supported) {
  // Render Apple Pay button
}
```

## Payment Form

`XMoney.paymentForm()` renders a full-featured payment form with card input, Google Pay, and Apple Pay in a single embedded component.

### Initialization


```javascript
const paymentForm = await window.XMoney.paymentForm({
  // Required
  container: '#payment-form-container', // element id, CSS selector, or HTMLElement
  orderPayload: '...',                  // Received from your server
  orderChecksum: '...',                 // Received from your server
  publicKey: 'pk_test_12345',

  // Card configuration
  card: {
    validationMode: 'onChange', // 'onSubmit' | 'onChange' | 'onBlur' | 'onTouched'
    savedCards: {
      enabled: true,       // Show saved cards for returning customers
      optInVisible: false, // Show the save-card checkbox
    },
    submitButton: {
      visible: true,
      type: 'pay', // 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'subscribe' | 'topUp'
    },
  },

  // Digital wallets
  paymentMethods: {
    googlePay: { enabled: true },
    applePay: { enabled: true },
  },

  // Appearance & locale
  options: {
    locale: 'en-US', // 'en-US' | 'el-GR' | 'ro-RO'
    appearance: {
      theme: 'custom', // 'light' | 'dark' | 'custom'
      variables: {
        colorPrimary: '#6366f1',
      },
    },
  },

  // Lifecycle callbacks (all optional)
  onReady: () => console.log('Form is ready'),
  onError: (err) => console.error('Form error', err),
  onPaymentProcessing: (isProcessing) => console.log('Processing...', isProcessing),
  onPaymentComplete: (result) => console.log('Payment completed', result),
})
```

### Configuration Properties

| Property | Type | Required | Description |
|  --- | --- | --- | --- |
| `container` | `string | HTMLElement` | **Yes** | DOM element ID, CSS selector, or `HTMLElement` for the form host. |
| `publicKey` | `string` | **Yes** | Your Site ID public key (`pk_test_...` or `pk_live_...`). |
| `orderPayload` | `string` | **Yes** | Base64-encoded encrypted order data from your backend. |
| `orderChecksum` | `string` | **Yes** | HMAC signature of the payload from your backend. |
| `card` | `object` | No | Card input configuration (see below). |
| `paymentMethods` | `object` | No | Enable/configure Google Pay and Apple Pay. |
| `options` | `object` | No | Locale and appearance customization. |
| `onReady` | `() => void` | No | Called when the form is ready. |
| `onError` | `(err) => void` | No | Called on initialization errors. |
| `onPaymentProcessing` | `(isProcessing: boolean) => void` | No | Called when the submission state changes. |
| `onPaymentComplete` | `(data) => void` | No | Called when payment completes (success or failure). |


### Card Options

| Option | Type | Default | Description |
|  --- | --- | --- | --- |
| `card.validationMode` | `'onSubmit' | 'onChange' | 'onBlur' | 'onTouched'` | `'onChange'` | When validation triggers. |
| `card.savedCards.enabled` | `boolean` | `true` | Display saved cards for returning customers. |
| `card.savedCards.optInVisible` | `boolean` | `true` | Show the save-card checkbox on new-card entry. |
| `card.submitButton.visible` | `boolean` | `true` | If `false`, you must call `instance.submit()` manually. |
| `card.submitButton.type` | `'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'subscribe' | 'topUp'` | `'pay'` | Label on the built-in submit button. |
| `card.cardHolderVerification` | `object` | — | Optional cardholder name verification handler. |


### Instance Methods


```javascript
// Update order details (e.g., after cart total changes)
paymentForm.updateOrder({
  orderPayload: 'new-base64-payload',
  orderChecksum: 'new-checksum',
})

// Update appearance at runtime
paymentForm.updateAppearance({
  theme: 'dark',
  variables: { colorPrimary: '#ff5722' },
})

// Change language
paymentForm.updateLocale('el-GR')

// Programmatically submit (useful when submitButton.visible is false)
paymentForm.submit()

// Validate without submitting
const { isValid, errors } = paymentForm.validate()

// Cleanup — always call on unmount
paymentForm.destroy()
```

## Payment Card

`XMoney.paymentCard()` renders **only** the card input element. Use it when building a custom multi-method checkout where you control the surrounding layout and additional payment buttons.


```javascript
const paymentCard = await window.XMoney.paymentCard({
  container: '#payment-card-container',
  orderPayload: payload,
  orderChecksum: checksum,
  publicKey: 'pk_test_your_key',

  card: {
    validationMode: 'onChange',
    savedCards: {
      enabled: false,
      optInVisible: true,
    },
    submitButton: {
      visible: true,
      type: 'pay',
    },
  },

  options: {
    locale: 'en-US',
    appearance: {
      theme: 'dark',
      variables: { colorPrimary: '#1976d2' },
    },
  },

  onReady: () => console.log('Payment card ready'),
  onError: (err) => console.error('Payment card error:', err),
  onPaymentProcessing: (isProcessing) => console.log('Processing:', isProcessing),
  onPaymentComplete: (result) => console.log('Payment successful:', result),
})

paymentCard.submit()
const { isValid, errors } = await paymentCard.validate()
paymentCard.destroy()
```

The `paymentCard` config is identical to `paymentForm`, except:

- No `paymentMethods` option (card only — add Apple Pay / Google Pay separately via their own factories).
- `submitButton.visible` controls whether the built-in submit button is shown.


## Apple Pay

Renders a native Apple Pay button. Always check capabilities first — the button should only be shown if the user's device and browser support Apple Pay.


```javascript
const applePay = await window.XMoney.applePay({
  container: '#apple-pay-button',
  orderPayload: payload,
  orderChecksum: checksum,
  publicKey: 'pk_test_your_key',

  options: {
    locale: 'en-US',
    appearance: {
      style: 'black', // 'white' | 'black' | 'white-outline'
      radius: 12,
      type: 'pay',
    },
  },

  onReady: () => console.log('Apple Pay button ready'),
  onError: (err) => console.error('Apple Pay error:', err),
  onPaymentProcessing: (isProcessing) => console.log('Processing:', isProcessing),
  onPaymentComplete: (result) => console.log('Apple Pay completed:', result),
})

applePay.updateOrder({ orderPayload: 'new-payload', orderChecksum: 'new-checksum' })
applePay.destroy()
```

**Apple Pay button types**: `'add-money'`, `'book'`, `'buy'`, `'checkout'`, `'contribute'`, `'continue'`, `'donate'`, `'order'`, `'plain'`, `'pay'`, `'reload'`, `'rent'`, `'set-up'`, `'subscribe'`, `'support'`, `'tip'`, `'top-up'`.

## Google Pay

Renders a native Google Pay button.


```javascript
const googlePay = await window.XMoney.googlePay({
  container: '#google-pay-button',
  orderPayload: payload,
  orderChecksum: checksum,
  publicKey: 'pk_test_your_key',

  options: {
    locale: 'en-US',
    appearance: {
      color: 'black',          // 'white' | 'black'
      radius: 12,
      type: 'pay',             // 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'plain' | 'pay' | 'subscribe'
      borderType: 'no_border', // 'default_border' | 'no_border'
    },
  },

  onReady: () => console.log('Google Pay button ready'),
  onError: (err) => console.error('Google Pay error:', err),
  onPaymentProcessing: (isProcessing) => console.log('Processing:', isProcessing),
  onPaymentComplete: (result) => console.log('Google Pay completed:', result),
})

googlePay.updateOrder({ orderPayload: 'new-payload', orderChecksum: 'new-checksum' })
googlePay.destroy()
```

## Saved Card Payment

`XMoney.savedCardPayment()` is **headless** — it has no `container` and no UI. Use it to trigger a charge against a previously saved card by its ID (for example, from a one-click "Pay with saved card" button you render yourself).


```javascript
const savedCard = await window.XMoney.savedCardPayment({
  orderPayload: payload,
  orderChecksum: checksum,
  publicKey: 'pk_test_your_key',

  onReady: () => console.log('Saved card payment ready'),
  onError: (err) => console.error('Saved card payment error:', err),
  onPaymentProcessing: (isProcessing) => console.log('Processing:', isProcessing),
  onPaymentComplete: (result) => console.log('Payment successful:', result),
})

// Trigger payment with a saved card
savedCard.pay({ cardId: 42 })

savedCard.destroy()
```

## Common Instance Methods

All SDK instances returned by the factories share these base methods:

### `updateOrder({ orderPayload, orderChecksum }): void`

Updates transaction details (e.g., when the user changes the cart total) without reloading the iframe.


```javascript
instance.updateOrder({
  orderPayload: newPayload,
  orderChecksum: newChecksum,
})
```

**Use cases**: cart updates, discount codes, shipping changes, dynamic pricing.

> **Note**: `XMoney.savedCardPayment()` does not support `updateOrder` — create a fresh instance for a new order instead.


### `destroy(): void`

Removes the iframe and cleans up event listeners. **Always call this when the component unmounts** to prevent memory leaks.


```javascript
// React useEffect cleanup example
useEffect(() => {
  let instance
  (async () => {
    instance = await window.XMoney.paymentForm({ /* ... */ })
  })()

  return () => {
    instance?.destroy() // Critical: prevents memory leaks
  }
}, [])
```

### `paymentForm` / `paymentCard` extras

- `submit()` — Programmatically submit the form.
- `validate()` — Run validation without submitting. Returns `{ isValid, errors }`.
- `updateAppearance(appearance)` — Update theme/variables at runtime.
- `updateLocale(locale)` — Change the form language at runtime.


### `applePay` / `googlePay` extras

- `updateOrder({ orderPayload, orderChecksum })` — Refresh the order for the button.


### `savedCardPayment` extras

- `pay({ cardId })` — Trigger a charge against a saved card.


## Customization (Theming)

`paymentForm` and `paymentCard` support full appearance customization via `options.appearance`.


```javascript
options: {
  locale: 'en-US', // 'en-US' | 'el-GR' | 'ro-RO'
  appearance: {
    theme: 'custom', // 'light' | 'dark' | 'custom'
    variables: {
      colorPrimary: '#0d9488',         // Brand color (focus, buttons)
      colorDanger: '#ef4444',          // Error text
      colorBackground: '#ffffff',      // Form background
      colorText: '#1f2937',            // Primary text
      colorTextSecondary: '#6b7280',   // Labels / hints
      colorTextPlaceholder: '#bdbdbd', // Placeholder text
      colorBorder: '#e5e7eb',          // Input borders
      colorBorderFocus: '#0d9488',     // Active input border
      colorBackgroundFocus: '#f0fdfa', // Focused input background
      fontFamily: 'Inter, system-ui, sans-serif',
      borderRadius: '8px',
    },
    rules: {
      '.xmoney-input:hover': {
        'box-shadow': '0 2px 4px rgba(0,0,0,0.1)',
      },
    },
  },
}
```

**Theme Options**:

- `'light'` — Default light theme (no variables needed).
- `'dark'` — Dark theme optimized for dark backgrounds.
- `'custom'` — Use your own color variables.


**Best Practice**: When using `'custom'`, provide all color variables for consistency. The SDK uses sensible defaults for any missing variables.

### Button Customization

Apple Pay and Google Pay buttons support their own appearance options:


```javascript
// Apple Pay
options: {
  appearance: {
    style: 'black', // 'white' | 'black' | 'white-outline'
    radius: 12,
    type: 'pay',
  },
}

// Google Pay
options: {
  appearance: {
    color: 'black',          // 'white' | 'black'
    radius: 12,
    type: 'pay',             // 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'plain' | 'pay' | 'subscribe'
    borderType: 'no_border', // 'default_border' | 'no_border'
  },
}
```

## Callbacks

All callbacks are optional. They are accepted by every factory (the saved-card factory omits only those that are UI-specific).

| Callback | Type | Description |
|  --- | --- | --- |
| `onReady()` | `() => void` | Fired when the embedded UI has loaded and is interactive. |
| `onError(err)` | `(err: { code: number; message: string } | string) => void` | Fired when initialization fails (e.g., invalid configuration, network errors). **Not** fired for transaction errors — those are delivered via `onPaymentComplete`. |
| `onPaymentProcessing(isProcessing)` | `(isProcessing: boolean) => void` | Fired when the component starts (`true`) or ends (`false`) network activity. Use it to show/hide loading spinners. |
| `onPaymentComplete(data)` | `(data: TransactionDetails) => void` | Fired when payment completes, regardless of success or failure. Inspect the `transactionStatus` field to determine the outcome. |


Important security note for onPaymentComplete
While `onPaymentComplete` is useful for updating your UI (e.g., redirecting to a "Success" page), you must not use it as the sole confirmation to release goods or services.

**Always validate the final transaction status server-side** by listening to the Instant Payment Notification (IPN) or querying the xMoney API. Server-to-server communication is signed and secure, ensuring the transaction status has not been altered by the client.

## Example Usage

### Basic Integration


```javascript
const paymentForm = await window.XMoney.paymentForm({
  container: '#payment-form-widget',
  publicKey: 'pk_test_your_key',
  orderPayload: payload,
  orderChecksum: checksum,

  onReady: () => {
    console.log('Form is ready')
    document.getElementById('payment-form-widget').style.opacity = '1'
  },
  onError: (error) => {
    console.error('Form initialization error:', error)
    alert('Failed to load payment form. Please refresh the page.')
  },
  onPaymentComplete: (data) => {
    console.log('Payment completed:', data)
    // Always confirm the final status with a backend call.
    if (data.transactionStatus === 'complete-ok') {
      window.location.href = '/checkout/success'
    } else {
      alert('Payment failed. Please try again.')
    }
  },
})
```

### With Custom Theme


```javascript
const paymentForm = await window.XMoney.paymentForm({
  container: '#payment-form-widget',
  publicKey: 'pk_test_your_key',
  orderPayload: payload,
  orderChecksum: checksum,
  options: {
    appearance: {
      theme: 'custom',
      variables: {
        colorPrimary: '#10b981',
        colorDanger: '#ef4444',
        borderRadius: '12px',
      },
    },
  },
})
```

### Custom Multi-Method Checkout

Compose your own layout by mounting each payment method separately:


```javascript
const capabilities = await window.XMoney.getPaymentMethodCapabilities()

const card = await window.XMoney.paymentCard({
  container: '#card',
  orderPayload: payload,
  orderChecksum: checksum,
  publicKey: 'pk_test_your_key',
  onPaymentComplete: (data) => handleResult(data),
})

if (capabilities.applePay.supported) {
  await window.XMoney.applePay({
    container: '#apple-pay',
    orderPayload: payload,
    orderChecksum: checksum,
    publicKey: 'pk_test_your_key',
    onPaymentComplete: (data) => handleResult(data),
  })
}

if (capabilities.googlePay.supported) {
  await window.XMoney.googlePay({
    container: '#google-pay',
    orderPayload: payload,
    orderChecksum: checksum,
    publicKey: 'pk_test_your_key',
    onPaymentComplete: (data) => handleResult(data),
  })
}
```

### TypeScript Support

The v2 SDK ships with full TypeScript definitions:


```typescript
import type {
  XMoneyPaymentFormConfig,
  XMoneyPaymentFormInstance,
} from './types/xmoney-sdk/payment-form-sdk.types'
import type { TransactionDetails } from './types/checkout.types'

const config: XMoneyPaymentFormConfig = {
  container: '#payment-form',
  orderChecksum: checksum,
  orderPayload: payload,
  publicKey: PUBLIC_KEY,
  onPaymentComplete: (result: TransactionDetails) => {
    console.log(result.id, result.transactionStatus)
  },
}

const instance: XMoneyPaymentFormInstance = await window.XMoney.paymentForm(config)
```

## Backend API Integration

To initialize Embedded Checkout, create a payment intent on your server that returns the encrypted `payload` and `checksum`.

### Backend Example


```typescript Node.js
const crypto = require('crypto')

function getBase64JsonRequest(orderData) {
  const jsonText = JSON.stringify(orderData)
  return Buffer.from(jsonText).toString('base64')
}

function getBase64Checksum(orderData, secretKey) {
  const hmacSha512 = crypto.createHmac('sha512', secretKey)
  hmacSha512.update(JSON.stringify(orderData))
  return hmacSha512.digest('base64')
}

// API route handler (Next.js, Express, etc.)
export default async function handler(req: any, res: any) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' })
  }

  const { amount, currency, description, publicKey } = req.body

  // API key is stored server-side (e.g., environment variable)
  const apiKey = process.env.XMONEY_SECRET_KEY

  if (!publicKey || !apiKey) {
    return res.status(400).json({ error: 'Missing credentials' })
  }

  const orderData = {
    publicKey: publicKey,
    customer: {
      identifier: 'customer-123', // Your internal customer ID
      firstName: 'John',
      lastName: 'Doe',
      country: 'RO', // ISO 3166-1 alpha-2
      city: 'Bucharest',
      email: 'john.doe@example.com',
    },
    order: {
      orderId: `order-${Date.now()}`,
      description: description,
      type: 'purchase',
      amount: amount, // Amount in smallest currency unit (cents)
      currency: currency, // ISO 4217 code (e.g., 'EUR', 'USD')
    },
    cardTransactionMode: 'authAndCapture',
    backUrl: 'https://yoursite.com/checkout/result',
  }

  try {
    const payload = getBase64JsonRequest(orderData)
    const checksum = getBase64Checksum(orderData, apiKey)

    res.json({ payload, checksum })
  } catch (error) {
    console.error('Error creating payment intent:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
}
```


```php PHP
<?php

function getBase64JsonRequest(array $orderData) {
    return base64_encode(json_encode($orderData));
}

function getBase64Checksum(array $orderData, $secretKey) {
    $hmacSha512 = hash_hmac('sha512', json_encode($orderData), $secretKey, true);
    return base64_encode($hmacSha512);
}

// API route handler
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['error' => 'Method not allowed']);
    exit;
}

$input = json_decode(file_get_contents('php://input'), true);
$amount = $input['amount'] ?? 100;
$currency = $input['currency'] ?? 'EUR';
$description = $input['description'] ?? 'Test Order';
$publicKey = $input['publicKey'] ?? null;

// API key is stored server-side (e.g., environment variable)
$apiKey = $_ENV['XMONEY_SECRET_KEY'] ?? getenv('XMONEY_SECRET_KEY');

if (!$publicKey || !$apiKey) {
    http_response_code(400);
    echo json_encode(['error' => 'Missing credentials']);
    exit;
}

$orderData = [
    'publicKey' => $publicKey,
    'customer' => [
        'identifier' => 'customer-123', // Your internal customer ID
        'firstName' => 'John',
        'lastName' => 'Doe',
        'country' => 'RO', // ISO 3166-1 alpha-2
        'city' => 'Bucharest',
        'email' => 'john.doe@example.com',
    ],
    'order' => [
        'orderId' => 'order-' . time(),
        'description' => $description,
        'type' => 'purchase',
        'amount' => $amount, // Amount in smallest currency unit (cents)
        'currency' => $currency, // ISO 4217 code (e.g., 'EUR', 'USD')
    ],
    'cardTransactionMode' => 'authAndCapture',
    'backUrl' => 'https://yoursite.com/checkout/result',
];

try {
    $payload = getBase64JsonRequest($orderData);
    $checksum = getBase64Checksum($orderData, $apiKey);

    header('Content-Type: application/json');
    echo json_encode(['payload' => $payload, 'checksum' => $checksum]);
} catch (Exception $e) {
    error_log('Error creating payment intent: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error']);
}
```

### Frontend Integration


```javascript
async function initializeCheckout() {
  const response = await fetch('/api/create-payment-intent', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      amount: 10000, // Amount in cents
      currency: 'EUR',
      description: 'Order #12345',
      publicKey: 'pk_test_your_key',
    }),
  })

  const { payload, checksum } = await response.json()

  const paymentForm = await window.XMoney.paymentForm({
    container: '#payment-form-widget',
    publicKey: 'pk_test_your_key',
    orderPayload: payload,
    orderChecksum: checksum,
    onReady: () => console.log('Form ready'),
    onPaymentComplete: (data) => {
      console.log('Payment completed:', data)
    },
  })
}
```

**Important**: The secret key (`sk_...`) should always be stored server-side (e.g., environment variables) and never exposed to the frontend.

## Browser Support

- Chrome / Edge (latest)
- Firefox (latest)
- Safari (latest)
- Apple Pay requires Safari on macOS or iOS
- Google Pay requires Chrome or other supported browsers


## Security

- All payment data is handled securely by the xMoney SDK
- Order checksums validate request integrity
- No sensitive card data is stored in your application
- PCI DSS compliant integration