Inline Checkout keeps most 3D Secure and payment-result handling inside the SDK. Your page listens to callbacks, updates the shopper experience, and confirms the final transaction status on the backend.
By default, options.enableBackgroundRefresh is true.
await window.XMoney.paymentForm({
container: 'xmoney-payment-form',
publicKey,
orderPayload,
orderChecksum,
options: {
enableBackgroundRefresh: true,
},
onPaymentProcessing(isProcessing) {
setLoading(isProcessing)
},
onPaymentComplete(transaction) {
handlePaymentResult(transaction)
},
onError(error) {
showPaymentError(error)
},
})With background refresh enabled:
- The SDK submits the payment inside the secure iframe.
- If 3DS is required, the SDK opens the authentication flow in a modal.
- The secure iframe polls the transaction result in the background.
- The SDK calls
onPaymentComplete(transaction)when the transaction reaches a complete status. - The SDK destroys the widget after
onPaymentComplete.
This is the recommended behavior for most website and WebView integrations.
If your checkout page sends a Content Security Policy, make sure it allows the SDK's iframe-based 3DS modal to load external 3DS pages.
3DS authentication can navigate through xMoney Secure, payment provider pages, directory servers, and card issuer ACS pages. Issuer and ACS hostnames vary by card, region, and provider configuration, so a fixed allowlist that contains only xMoney domains can block valid challenges.
For iframe-based 3DS, your frame-src policy should allow HTTPS frame destinations:
Content-Security-Policy: frame-src 'self' https:;If you already use a stricter policy, keep your existing trusted frame sources and add https: to frame-src. For older browser coverage, mirror the same value in child-src if your CSP uses that directive.
Do not use frame-ancestors for this requirement. frame-ancestors controls who can embed your page, while frame-src controls what your checkout page may embed.
During a 3DS challenge, the modal iframe may need to render a provider or issuer-controlled page such as an ACS URL. If frame-src only allows xMoney domains, the browser can block the challenge before the shopper can authenticate.
| Callback | When it fires | Use it for |
|---|---|---|
onReady() | The iframe has loaded and initialized. | Enable the checkout UI. |
onPaymentProcessing(isProcessing) | The SDK starts or stops payment work. | Disable buttons, show loaders, prevent duplicate submission. |
onPaymentComplete(transaction) | The final transaction status is available to the iframe. | Route the shopper to a result state and ask your backend to confirm the order. |
onError(error) | Initialization, configuration, network, or polling errors occur. | Show retry guidance and log diagnostics. |
onPaymentComplete can include successful and failed results. Inspect transaction.transactionStatus.
function handlePaymentResult(transaction) {
switch (transaction.transactionStatus) {
case 'complete-ok':
window.location.href = `/checkout/success?orderId=${transaction.externalOrderId}`
break
case 'complete-failed':
showPaymentError('Payment failed. Please try another payment method.')
break
default:
showPaymentPending('Payment submitted. We are confirming the final status.')
}
}Browser callbacks are useful for the user experience, but fulfillment should depend on trusted server-side data.
Use one of these server-side confirmation paths:
- Handle the xMoney webhook or IPN for the final transaction.
- Query the transaction status from your backend before fulfilling the order.
- Store the xMoney
transactionId, yourorderId, and the final status together for reconciliation.
The browser can be closed, refreshed, blocked by extensions, or tampered with. Always confirm the final transaction result from your backend before releasing goods or services.
If you set enableBackgroundRefresh to false, the SDK no longer waits for the final result in the background.
await window.XMoney.paymentForm({
container: 'xmoney-payment-form',
publicKey,
orderPayload,
orderChecksum,
options: {
enableBackgroundRefresh: false,
},
})With this mode:
- If 3DS is required, the SDK opens the 3DS modal.
- If no modal flow is required, the parent page can be redirected to the returned URL.
- Your
backUrland backend result page become responsible for the final shopper experience.
Use this only when your checkout flow is designed around redirects. For most inline checkout pages, keep background refresh enabled.
A failed transaction should usually create a new order payload and checksum before retrying. This ensures the SDK starts with fresh signed order data.
async function retryPayment() {
paymentForm?.destroy()
const nextIntent = await createCheckoutIntent()
paymentForm = await window.XMoney.paymentForm({
container: 'xmoney-payment-form',
publicKey: nextIntent.publicKey,
orderPayload: nextIntent.orderPayload,
orderChecksum: nextIntent.orderChecksum,
onPaymentComplete: handlePaymentResult,
onError: showPaymentError,
})
}In mobile WebViews, 3DS can send the app to another app or browser context temporarily. Preserve the checkout page state so the flow can continue when the app returns to the foreground.
Recommended behavior:
- Keep
enableBackgroundRefreshenabled. - Do not destroy the WebView while a payment is processing.
- Preserve the checkout URL and signed payload across app backgrounding.
- Let the SDK complete the modal or result polling before closing the WebView.
See Mobile WebView integration for platform guidance.