Payments in an iframe (Iframe SDK)
Iframe mode lets you embed a card payment form (manual entry) and Apple Pay and Google Pay buttons directly on your website, without redirecting the customer to an external payment gateway.
Benefits:
- No redirect - the customer stays on your website
- Full control over UX - the form or button is embedded in your layout
- A few lines of code - quick integration through the JavaScript SDK
- Security - card data is encrypted with RSA inside the iframe, and the merchant never handles it
Iframe integration does not require PCI DSS certification. Card data is entered and encrypted exclusively inside the dpay iframe and never reaches your server. If you need full control over the form and process card data in your own infrastructure, see the Server-to-Server (S2S) integration. That option requires PCI DSS certification.
Supported methods
| Method | Description | method value |
|---|---|---|
| Card (manual entry) | Complete form: cardholder details, card number, expiry date, and CVC | CARD |
| Card (inline fields) | Card fields only (number, expiry date, and CVC), embedded in your form | CARD_INLINE |
| Apple Pay | Apple Pay button | APPLE_PAY |
| Google Pay | Google Pay button | GOOGLE_PAY |
How it works
/api/v1_0/cards/payment/{transactionId}/pay/card-otpContract for the card payment performed by the iframe SDK.Full contract in the API Reference
Quick start
Step 1: Register the payment
Register the transaction through the standard API:
curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "49.99",
"url_success": "https://myshop.example/success",
"url_fail": "https://myshop.example/error",
"url_ipn": "https://myshop.example/api/ipn",
"checksum": "..."
}'
Save the transactionId from the response.
Step 2: Add the SDK to your page
<script src="https://gateway.dpay.pl/sdk/dpay-iframe-sdk.js"></script>
For the test environment, use https://gateway.snd.dpay.pl/sdk/dpay-iframe-sdk.js.
Step 3: Check method availability (optional)
For Apple Pay and Google Pay, it is worth checking whether the method is available on the customer's device:
if (DPayIframeSDK.isApplePayAvailable()) {
// Show the Apple Pay button
}
if (DPayIframeSDK.isGooglePayAvailable()) {
// Show the Google Pay button
}
Apple Pay availability:
- iOS, macOS, Windows, Linux - available
- Android - unavailable
Google Pay availability:
- Android, Desktop, iOS (Chrome/Edge) - available
- iOS Safari - unavailable
Step 4: Initialize the SDK
const sdk = new DPayIframeSDK({
transactionId: 'your-transaction-id',
method: 'CARD', // or 'CARD_INLINE', 'APPLE_PAY', 'GOOGLE_PAY'
containerId: 'payment-container',
environment: 'production',
onIframeReady: (data) => {
console.log('Iframe ready', data);
},
onPaymentSuccess: (data) => {
console.log('Payment successful!', data);
window.location.href = '/success';
},
onPaymentError: (data) => {
console.error('Payment error:', data.error);
},
});
sdk.init();
SDK configuration
Required parameters
| Parameter | Type | Description |
|---|---|---|
transactionId | string | Transaction UUID from Step 1 |
method | string | Payment method: CARD, CARD_INLINE, APPLE_PAY, or GOOGLE_PAY |
containerId or container | string / HTMLElement | DOM element ID or a reference to the element where the iframe will be embedded |
Optional parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
locale | string | 'pl' | Form language |
environment | string | 'production' | Environment: 'production' or 'sandbox' |
baseUrl | string | automatic | Gateway base URL (derived from environment) |
iframeStyles | object | {} | Custom CSS styles for the iframe |
timeout | number | 300000 | Timeout in milliseconds (5 minutes by default) |
debug | boolean | false | Debug logging in the browser console |
autoResize | boolean | true | Automatically adjust the iframe height to its content |
The default environment is 'production': omitting environment sends the SDK to the production gateway. For testing, explicitly set environment: 'sandbox'. To keep the configuration clear, we recommend always specifying environment.
CARD-specific parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
threeDsMode | string | 'iframe' | 3D Secure mode: 'iframe' (inside the frame) or 'redirect' (redirect) |
firstName | string | '' | Cardholder's first name (pre-fill) |
lastName | string | '' | Cardholder's last name (pre-fill) |
email | string | '' | Cardholder's email address (pre-fill) |
CARD_INLINE-specific parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
threeDsMode | string | 'modal' | 3D Secure mode: 'modal' (a modal overlay over the page) or 'redirect' (redirect) |
layout | string | 'stacked' | Field layout: 'stacked' (one below another) or 'single-row' (one row) |
showErrors | boolean | false | Whether the iframe displays field validation messages; when false, handle them in onValidationError |
labels | boolean | false | Whether the iframe displays field labels |
fieldStyles | object | {} | CSS styles for card fields (passed to the iframe) |
Callbacks
| Callback | Parameters | Description |
|---|---|---|
onIframeReady | { transactionId, method } | Iframe loaded and ready |
onPaymentStarted | { transactionId, method } | The user started the payment |
onPaymentSuccess | { transactionId, status } | Payment completed successfully |
onPaymentError | { transactionId, error, code? } | Payment error |
onPaymentCancelled | { transactionId } | The user cancelled the payment |
on3dsRedirect | { ... } | 3D Secure redirect (threeDsMode: 'redirect') |
onIframeError | { error } | Iframe loading error |
onIframeResize | { height } | Iframe size change (handled automatically when autoResize: true) |
onFieldChange | { field, ... } | Card field state change (CARD_INLINE only) |
onValidationError | { fields: [{ field, error }] } | Field validation errors (CARD_INLINE only) |
SDK methods
init()
Initializes the SDK, creates an iframe, and renders the form or button in the container.
sdk.init();
destroy()
Removes the iframe and clears event listeners. Call it when unmounting the component.
sdk.destroy();
reload()
Reloads the iframe, for example after a configuration change.
sdk.reload();
getStatus()
Returns the current SDK status.
const status = sdk.getStatus();
// {
// status: 'idle' | 'loading' | 'ready' | 'processing' | 'success' | 'error',
// isInitialized: boolean,
// transactionId: string,
// method: string
// }
sendMessage(message)
Sends a message to the iframe through postMessage.
sdk.sendMessage({ type: 'custom:action', data: { ... } });
submit(personalData) (CARD_INLINE only)
Submits the payment with cardholder data collected in your form. The required fields are firstName, lastName, email, and acceptTerms. Missing fields are reported through onValidationError without submitting the payment.
sdk.submit({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
acceptTerms: true,
});
focus(field) (CARD_INLINE only)
Focuses the selected card field: 'ccn' (number), 'expiry' (expiry date), or 'cvc'.
sdk.focus('ccn');
clear() (CARD_INLINE only)
Clears the values of the card fields in the iframe.
sdk.clear();
Events (postMessage)
The SDK communicates with the iframe through window.postMessage. Events are handled automatically and passed to the appropriate callbacks.
| Event type | Callback | Description |
|---|---|---|
iframe:ready | onIframeReady | Iframe loaded and ready |
iframe:resize | onIframeResize | Iframe content size changed |
iframe:error | onIframeError | Iframe error |
payment:started | onPaymentStarted | Payment started |
payment:success | onPaymentSuccess | Payment successful |
payment:error | onPaymentError | Payment error |
payment:cancelled | onPaymentCancelled | Payment cancelled |
payment:3ds-redirect | on3dsRedirect | 3DS redirect |
payment:3ds-modal | (handled internally) | Open the 3DS modal overlay (CARD_INLINE with threeDsMode: 'modal') |
inline:field-change | onFieldChange | Card field state changed (CARD_INLINE) |
inline:validation-error | onValidationError | Field validation errors (CARD_INLINE) |
Integration examples
- Vanilla JS
- React
- Vue
<!DOCTYPE html>
<html>
<head>
<title>Payment</title>
</head>
<body>
<div id="payment-container"></div>
<script src="https://gateway.dpay.pl/sdk/dpay-iframe-sdk.js"></script>
<script>
const sdk = new DPayIframeSDK({
transactionId: 'abc-123-def',
method: 'CARD',
containerId: 'payment-container',
onPaymentSuccess: (data) => {
window.location.href = '/success';
},
onPaymentError: (data) => {
alert('Payment error: ' + data.error);
},
});
sdk.init();
</script>
</body>
</html>
import { useEffect, useRef } from 'react';
function PaymentIframe({ transactionId, method = 'CARD' }) {
const containerRef = useRef(null);
const sdkRef = useRef(null);
useEffect(() => {
const sdk = new window.DPayIframeSDK({
transactionId,
method,
container: containerRef.current,
onPaymentSuccess: (data) => {
console.log('Payment successful:', data);
},
onPaymentError: (data) => {
console.error('Error:', data.error);
},
});
sdk.init();
sdkRef.current = sdk;
return () => sdk.destroy();
}, [transactionId, method]);
return <div ref={containerRef} />;
}
<template>
<div ref="paymentContainer"></div>
</template>
<script>
export default {
props: ['transactionId', 'method'],
mounted() {
this.sdk = new window.DPayIframeSDK({
transactionId: this.transactionId,
method: this.method || 'CARD',
container: this.$refs.paymentContainer,
onPaymentSuccess: (data) => {
this.$emit('success', data);
},
onPaymentError: (data) => {
this.$emit('error', data);
},
});
this.sdk.init();
},
beforeUnmount() {
this.sdk?.destroy();
},
};
</script>
CARD integration (manual entry)
The CARD method displays a complete card payment form in the iframe. The form collects:
- Cardholder's first and last name
- Email address
- Card number
- Expiry date
- CVC/CVV code
Card data is encrypted with RSA inside the iframe. Your website never has access to raw card data. You do not need PCI DSS certification.
Pre-filling cardholder data
You can pre-fill the cardholder's details if you already know them, for example from the checkout process:
const sdk = new DPayIframeSDK({
transactionId: 'abc-123-def',
method: 'CARD',
containerId: 'payment-container',
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
onPaymentSuccess: (data) => {
window.location.href = '/success';
},
});
sdk.init();
Dynamic Currency Conversion (DCC)
If the cardholder pays with a foreign card and DCC is enabled for your service, the iframe SDK handles the entire process internally. The customer sees the currency selection screen inside the iframe, and after they make a decision the SDK continues the flow to payment:success or a 3DS challenge. The merchant does not need to handle any additional callbacks or views.
For the complete API contract and UI requirements when integrating without an iframe (S2S), see Dynamic Currency Conversion (DCC).
Handling 3D Secure
The threeDsMode parameter controls how 3D Secure verification is handled:
'iframe'(default) - 3DS verification takes place inside the iframe, and the customer stays on your website'redirect'- the customer is redirected to the bank's website and then returns tourl_success/url_fail
const sdk = new DPayIframeSDK({
transactionId: 'abc-123-def',
method: 'CARD',
containerId: 'payment-container',
threeDsMode: 'iframe', // or 'redirect'
on3dsRedirect: (data) => {
// Called only when threeDsMode is 'redirect'
console.log('3DS redirect:', data);
},
onPaymentSuccess: (data) => {
window.location.href = '/success';
},
});
sdk.init();
CARD_INLINE integration (card fields in your form)
The CARD_INLINE method embeds only the card fields (number, expiry date, and CVC) in an iframe. You build the rest of the form (cardholder data, consents, and the payment button) on your side and submit the payment through sdk.submit(...). Card data still never leaves the dpay iframe, so PCI DSS certification is not required.
const sdk = new DPayIframeSDK({
transactionId: 'abc-123-def',
method: 'CARD_INLINE',
containerId: 'card-fields',
environment: 'production',
threeDsMode: 'modal', // 'modal' (overlay) or 'redirect'
layout: 'single-row', // 'stacked' or 'single-row'
showErrors: false, // handle validation messages yourself
labels: false, // no field labels inside the iframe
fieldStyles: { // card field styling
fontSize: '16px',
},
onFieldChange: (data) => {
// For example, update the state of the "Pay" button
},
onValidationError: (data) => {
data.fields.forEach(({ field, error }) => {
// Show the error next to the corresponding form field
});
},
onPaymentSuccess: (data) => {
window.location.href = '/success';
},
onPaymentError: (data) => {
console.error('Payment error:', data.error);
},
});
sdk.init();
// After the customer clicks your "Pay" button:
document.getElementById('pay-button').addEventListener('click', () => {
sdk.submit({
firstName: document.getElementById('first-name').value,
lastName: document.getElementById('last-name').value,
email: document.getElementById('email').value,
acceptTerms: document.getElementById('terms').checked,
});
});
With threeDsMode: 'modal', the 3D Secure challenge opens automatically in a modal overlay above your page. If the customer closes the overlay, onPaymentCancelled is called. With threeDsMode: 'redirect', the customer goes to the bank's website and returns to url_success / url_fail.
Security
Content Security Policy (CSP)
Add the dpay gateway domain to the frame-src directive:
<meta http-equiv="Content-Security-Policy"
content="frame-src https://gateway.dpay.pl https://gateway.snd.dpay.pl;">
HTTPS
The iframe SDK requires your website to use HTTPS. Apple Pay and Google Pay do not work on HTTP pages.
iframe sandbox attributes
The SDK automatically applies these iframe security attributes:
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
allow="payment"
Filtering postMessage messages
The SDK verifies incoming postMessage messages on two levels: by source (it processes only messages from the window of the iframe it embedded, using event.source) and by origin (event.origin must match the gateway origin derived from baseUrl; messages from other origins are rejected). Messages sent to the iframe use the gateway's specific targetOrigin, not '*'. If your application also listens for message events directly alongside the SDK, verify event.origin against the dpay gateway address (https://gateway.dpay.pl or https://gateway.snd.dpay.pl) before trusting the message contents.
Troubleshooting
The iframe does not load
- Check the CSP headers:
frame-srcmust include the dpay gateway domain - Verify
baseUrl/environmentin the SDK configuration - Check the Network tab in DevTools
The Apple Pay / Google Pay button does not appear
- Use
DPayIframeSDK.isApplePayAvailable()/isGooglePayAvailable()to check availability on the device - Check that the transaction has the correct payment channel
- Enable
debug: trueand check the browser console
The payment does not start
- Make sure the page uses HTTPS
- Check that
transactionIdis valid and that the transaction has not expired - Use the
onIframeErrorcallback for diagnostics
The CARD form does not display correctly
- Check that the container is wide enough (at least 320px)
- With
autoResize: false, set the iframe height manually (at least 580px for the CARD form) - Do not set
overflow: hiddenon the container, as it may crop 3DS modal windows
Testing in sandbox mode
When test mode is enabled for the service, the simulation scenario is selected by the card number entered in the form inside the iframe. Enter one of the test numbers exactly as a customer would enter a real card:
| Card number | Scenario |
|---|---|
4111111111111111 | Visa - payment completed successfully |
5555555555554444 | Mastercard - payment completed successfully |
4000000000000002 | Visa - declined |
4000000000000069 | Visa - expired card |
4000000000000127 | Visa - insufficient funds |
The full list of test numbers and details is available on the Test environment page.