Skip to content

The xMoney.js SDK (xmoney.js) powers Embedded Checkout, allowing you to securely embed the payment form 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 script on every page where you want to render the payment form.

<script src="https://secure.xmoney.com/sdk/v1/xmoney.js"></script>

About Embedded Checkout

Embedded Checkout is xMoney's solution for accepting payments directly on your website. The payment form is embedded in your page using an iframe, 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, and Apple Pay

For redirect-based checkout flows, see Hosted Checkout.

Initialization

To create an Embedded Checkout payment form, instantiate the XMoneyPaymentForm class.

const checkout = new window.XMoneyPaymentForm({
  // Required
  container: 'payment-form-widget',
  publicKey: 'pk_test_12345',
  orderPayload: '...', // Received from your server
  orderChecksum: '...', // Received from your server

  // Configuration
  options: {
    locale: 'en-US',
    buttonType: 'pay',
    validationMode: 'onChange',
    displaySubmitButton: true,
    displaySaveCardOption: true,
    enableSavedCards: true,
    // Wallets
    googlePay: { enabled: true },
    applePay: { enabled: true },
    // Styling
    appearance: {
      theme: 'custom',
      variables: {
        colorPrimary: '#6366f1',
      },
    },
  },

  // Lifecycle Callbacks (all optional)
  onReady: () => console.log('Form is ready'),
  onError: (error) => console.error('Form error', error),
  onPaymentComplete: (data) => console.log('Payment completed', data),
  onSubmitPending: (isPending) => console.log('Processing...', isPending),
})

Configuration Properties

PropertyTypeRequiredDescription
containerstringYesDOM element ID where the form will be rendered.
publicKeystringYesYour Site ID public key (pk_test_... or pk_live_...).
orderPayloadstringYesBase64-encoded encrypted order data from your backend.
orderChecksumstringYesHMAC signature of the payload from your backend.
optionsobjectNoCustomization options (see below).
onReadyfunctionNoCallback fired when form is ready.
onErrorfunctionNoCallback fired on form initialization errors (not transaction errors).
onPaymentCompletefunctionNoCallback fired when payment is completed (success or failure).
onSubmitPendingfunctionNoCallback fired when submission state changes.

Options

The options object controls the behavior and look of the form.

Functional Options

OptionTypeDefaultDescription
locale'en-US' | 'el-GR' | 'ro-RO''en-US'Language for labels and error messages.
displaySubmitButtonbooleantrueIf false, you must call checkout.submit() manually.
buttonTypestring'pay'Text on the submit button (e.g., 'pay', 'book', 'subscribe').
validationModestring'onChange'When validation triggers: 'onSubmit', 'onChange', 'onBlur', 'onTouched'.
displaySaveCardOptionbooleanfalseShow checkbox to save card for future use.
enableSavedCardsbooleanfalseDisplay saved cards for returning customers.
enableBackgroundRefreshbooleanfalseEnable automatic order status polling. When true, onPaymentComplete will not fire.

Saved Cards (One-Click Payment)

To enable one-click payments for returning customers:

  1. Set options.enableSavedCards: true.
  2. Set options.displaySaveCardOption: true (to allow saving new cards).
options: {
  enableSavedCards: true,
  displaySaveCardOption: true,
}

Digital Wallets

You can enable Google Pay and Apple Pay directly within the form.

Google Pay

options: {
  googlePay: {
    enabled: true,
    appearance: {
      color: "black", // "white" | "black"
      type: "pay",    // "buy", "book", "donate", etc.
      radius: 12,     // Corner radius in pixels (default: 12)
      borderType: "no_border" // "default_border" | "no_border" (default: "no_border")
    }
  }
}

Properties:

  • enabled (boolean): Enable Google Pay. Default: false
  • appearance.color: Button color. Defaults to 'black' in light theme, 'white' in dark theme
  • appearance.type: Button action type. Default: 'pay'
  • appearance.radius: Corner radius in pixels. Default: 12
  • appearance.borderType: Border style. Default: 'no_border'

Apple Pay

options: {
  applePay: {
    enabled: true,
    appearance: {
      style: "black", // "white" | "black" | "white-outline"
      type: "pay",    // See Apple Pay button types below
      radius: 12      // Corner radius in pixels (default: 12)
    }
  }
}

Properties:

  • enabled (boolean): Enable Apple Pay. Default: false
  • appearance.style: Button style. Defaults to 'black' in light theme, 'white' in dark theme
  • appearance.type: Button action type. Default: 'pay'
  • appearance.radius: Corner radius in pixels. Default: 12

Apple Pay Button Types: 'add-money', 'book', 'buy', 'checkout', 'contribute', 'continue', 'donate', 'order', 'plain', 'pay', 'reload', 'rent', 'set-up', 'subscribe', 'support', 'tip', 'top-up'

Note: Digital wallets only appear if supported by the user's device/browser and if the user has a wallet set up.

Customization (Theming)

Use the appearance object to match the form to your brand.

options: {
  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
      borderRadius: "8px"
    }
  }
}

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' theme, provide all color variables for consistency. The SDK uses sensible defaults for any missing variables.

Instance Methods

Once initialized, you can control the form instance programmatically.

updateOrder(payload: string, checksum: string): void

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

checkout.updateOrder(newPayload, newChecksum)

Use Cases:

  • Cart updates (add/remove items)
  • Applying discount codes
  • Changing shipping options
  • Dynamic pricing updates

updateLocale(locale: 'en-US' | 'el-GR' | 'ro-RO'): void

Dynamically changes the language.

checkout.updateLocale('el-GR') // Switch to Greek

Supported locales: 'en-US', 'el-GR', 'ro-RO'

updateAppearance(appearance: AppearanceOptions): void

Updates the theme at runtime.

// Switch to dark theme
checkout.updateAppearance({ theme: 'dark' })

// Update custom colors
checkout.updateAppearance({
  theme: 'custom',
  variables: {
    colorPrimary: '#10b981',
    borderRadius: '12px',
  },
})

submit(): void

Triggers the payment submission. Useful if displaySubmitButton is false.

checkout.submit()

close(): void

Hides/closes the form UI without destroying the instance. The form can be shown again later.

checkout.close()

destroy(): void

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

// React useEffect cleanup example
useEffect(() => {
  const checkout = new window.XMoneyPaymentForm({ /* ... */ })
  
  return () => {
    checkout.destroy() // Critical: prevents memory leaks
  }
}, [])

Callbacks

All callbacks are optional.

CallbackTypeDescription
onReady()() => voidFired when the iframe has fully loaded and is interactive.
onError(err)(error: string | Error) => voidFired when form initialization fails (e.g., invalid configuration, network errors during initialization). Note: This does not fire for transaction errors. Transaction errors are handled via onPaymentComplete.
onPaymentComplete(data)(data: object) => voidFired when payment is completed, regardless of success or failure. Receives the transaction result (e.g., { orderId: "...", status: "...", customerId: 123 }). Check the status field to determine if payment was successful.
onSubmitPending(isPending)(isPending: boolean) => voidFired when the form starts (true) or ends (false) network activity. Use to show/hide loading spinners.

Example Usage

The following examples demonstrate how to integrate Embedded Checkout into your application.

Basic Integration

const checkout = new window.XMoneyPaymentForm({
  container: 'payment-form-widget',
  publicKey: 'pk_test_your_key',
  orderPayload: payload, // From your server
  orderChecksum: checksum, // From your server
  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)
    // Check payment status
    if (data.status === 'complete-ok') {
      window.location.href = '/checkout/success'
    } else {
      alert('Payment failed. Please try again.')
    }
  },
})

With Custom Theme

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

Backend API Integration

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

Backend Example

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

Frontend Integration

// Fetch payment intent from your backend
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', // Public key can be sent from frontend
    }),
  })

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

  // Initialize Embedded Checkout
  const checkout = new window.XMoneyPaymentForm({
    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.