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.
Include the v2 SDK script on every page where you want to render a payment component.
<!-- 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:
window.XMoney.paymentForm(config)
window.XMoney.paymentCard(config)
window.XMoney.applePay(config)
window.XMoney.googlePay(config)
window.XMoney.savedCardPayment(config)
window.XMoney.getPaymentMethodCapabilities()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.
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 | — |
Integrating the v2 SDK into your application involves these steps:
- Load the SDK — Add the
xmoney.jsv2 script tag to your HTML - Create a Payment Intent — Call your backend to obtain
orderPayloadandorderChecksum - Initialize a payment method — Call the appropriate
window.XMoney.*()factory - Handle callbacks — Implement
onReady,onPaymentComplete,onPaymentProcessing,onError - Cleanup — Call
instance.destroy()when your component unmounts
Before rendering Apple Pay or Google Pay buttons, check whether the browser/device supports them:
const capabilities = await window.XMoney.getPaymentMethodCapabilities()
if (capabilities.googlePay.supported) {
// Render Google Pay button
}
if (capabilities.applePay.supported) {
// Render Apple Pay button
}XMoney.paymentForm() renders a full-featured payment form with card input, Google Pay, and Apple Pay in a single embedded component.
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),
})| 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). |
| 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. |
// 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()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.
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
paymentMethodsoption (card only — add Apple Pay / Google Pay separately via their own factories). submitButton.visiblecontrols whether the built-in submit button is shown.
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.
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'.
Renders a native Google Pay button.
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()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).
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()All SDK instances returned by the factories share these base methods:
Updates transaction details (e.g., when the user changes the cart total) without reloading the iframe.
instance.updateOrder({
orderPayload: newPayload,
orderChecksum: newChecksum,
})Use cases: cart updates, discount codes, shipping changes, dynamic pricing.
Note:
XMoney.savedCardPayment()does not supportupdateOrder— create a fresh instance for a new order instead.
Removes the iframe and cleans up event listeners. Always call this when the component unmounts to prevent memory leaks.
// React useEffect cleanup example
useEffect(() => {
let instance
(async () => {
instance = await window.XMoney.paymentForm({ /* ... */ })
})()
return () => {
instance?.destroy() // Critical: prevents memory leaks
}
}, [])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.
updateOrder({ orderPayload, orderChecksum })— Refresh the order for the button.
pay({ cardId })— Trigger a charge against a saved card.
paymentForm and paymentCard support full appearance customization via options.appearance.
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.
Apple Pay and Google Pay buttons support their own appearance options:
// 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'
},
}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. |
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.
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.')
}
},
})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',
},
},
},
})Compose your own layout by mounting each payment method separately:
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),
})
}The v2 SDK ships with full TypeScript definitions:
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)To initialize Embedded Checkout, create a payment intent on your server that returns the encrypted payload and checksum.
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' })
}
}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.
- Chrome / Edge (latest)
- Firefox (latest)
- Safari (latest)
- Apple Pay requires Safari on macOS or iOS
- Google Pay requires Chrome or other supported browsers
- 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