Using Virtual POS in iframe
We do not recommend using iframe integration. However, if you need to implement it, you must configure specific server and page settings to ensure proper functionality. Please review the technical requirements below carefully.
You can embed the Virtual POS in an iframe as shown in the example code below. The website loaded within the iframe must be your own domain. For testing purposes, you can use the example form from page 3 on your website.
<!doctype html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>iframe</title>
</head>
<body>
<div class="container">
<script src="https://paynkolaytest.nkolayislem.com.tr/VPos/js/paynkolay-iframe-resizer.js"></script>
<!-- iframe içinde çağırdığınız web sitesi sizin web siteniz olmalıdır. Burada sayfa 3'teki örnek formu kullanabilirsiniz. -->
<iframe src="https://paynkolay.com.tr/entegrasyon/sanal-pos-test-ortam-iframe" id="paynkolayiframe" frameborder="0" scrolling="no" style="width: 100%;"></iframe>
<script>iFrameResize({},'#paynkolayiframe');</script>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html><!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Sanal POS Test</title>
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
<link rel="stylesheet" href="/assets/css/bootstrap-5.3.8.min.css">
</head>
<body class="bg-light">
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST" and isset($_POST["sayfaya-gonder"]) and isset($_POST["sx"])) {
$sayfayagonder = test_input($_POST["sayfaya-gonder"]);
$sx = test_input($_POST["sx"]);
$successUrl = test_input($_POST["successUrl"]);
$failUrl = test_input($_POST["failUrl"]);
$amount = test_input($_POST["amount"]);
$clientRefCode = test_input($_POST["clientRefCode"]);
$use3D = test_input($_POST["use3D"]);
$rnd = test_input($_POST["rnd"]);
$transactionType = test_input($_POST["transactionType"]);
} else {
$sx = "100000000|pGi2dXPOwdimyVtqUEYqb7WPqQqQsvTeo7W3W8BVU8HMK3zLvP7IdNFOX3jGcMiCH6DadiOHaqOuLmdKbBedM93vY9D+YSKV/UXX/JYi0Ko6oGAzC1y1zVQ3SqNB/VSK8SSZxw==";
$successUrl = "https://paynkolay.com.tr/entegrasyon/sanal-pos-test-ortam-success.php";
$failUrl = "https://paynkolay.com.tr/entegrasyon/sanal-pos-test-ortam-fail.php";
$amount = "10.00";
$clientRefCode = rand(100000, 99999999);
$use3D = "";
$rnd = date("d-m-Y H:i:s");
$transactionType = "SALES";
}
$merchantSecretKey = "_sOme*ShaREd*SecreT";
$customerKey = "";
$hashstr = $sx . "|" . $clientRefCode . "|" . $amount . "|" . $successUrl . "|" . $failUrl . "|" . $rnd . "|" . $customerKey . "|" . $merchantSecretKey;
$hash = mb_convert_encoding($hashstr, 'UTF-8');
$hashedBytes = hash("sha512", $hash, true);
$hashDataV2 = base64_encode($hashedBytes);
?>
<div class="container py-4" style="max-width: 600px;">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-credit-card me-2"></i>Sanal POS Test</h5>
</div>
<div class="card-body">
<form action="https://paynkolaytest.nkolayislem.com.tr/Vpos" method="post">
<input type="hidden" name="sx" value="<?php echo htmlspecialchars($sx) ?>">
<div class="mb-3">
<label class="form-label fw-semibold">successUrl</label>
<input class="form-control" name="successUrl" value="<?php echo htmlspecialchars($successUrl) ?>">
</div>
<div class="mb-3">
<label class="form-label fw-semibold">failUrl</label>
<input class="form-control" name="failUrl" value="<?php echo htmlspecialchars($failUrl) ?>">
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-semibold">amount</label>
<input class="form-control" name="amount" value="<?php echo htmlspecialchars($amount) ?>">
</div>
<div class="col-6">
<label class="form-label fw-semibold">clientRefCode</label>
<input class="form-control" name="clientRefCode" value="<?php echo htmlspecialchars($clientRefCode) ?>">
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<label class="form-label fw-semibold">use3D</label>
<input class="form-control" name="use3D" value="<?php echo htmlspecialchars($use3D) ?>" placeholder="true / boş">
</div>
<div class="col-6">
<label class="form-label fw-semibold">transactionType</label>
<input class="form-control" name="transactionType" value="<?php echo htmlspecialchars($transactionType) ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">rnd</label>
<input class="form-control" name="rnd" value="<?php echo htmlspecialchars($rnd) ?>">
</div>
<div class="mb-3">
<label class="form-label fw-semibold text-muted">hashDataV2 <span class="badge bg-secondary">readonly</span></label>
<input class="form-control bg-body-secondary" readonly name="hashDataV2" value="<?php echo htmlspecialchars($hashDataV2) ?>">
</div>
<div class="d-grid">
<button type="submit" name="sanal-posa-gonder" class="btn btn-primary">
Sanal POS'a Gönder
</button>
</div>
</form>
</div>
</div>
</div>
<script src="/assets/js/bootstrap-5.3.8.bundle.min.js"></script>
<script>
(function() {
const form = document.querySelector('form');
const merchantSecretKey = '_sOme*ShaREd*SecreT';
const customerKey = '';
async function recalcHash() {
const sx = form.sx.value;
const clientRefCode = form.clientRefCode.value;
const amount = form.amount.value;
const successUrl = form.successUrl.value;
const failUrl = form.failUrl.value;
const rnd = form.rnd.value;
const hashstr = [sx, clientRefCode, amount, successUrl, failUrl, rnd, customerKey, merchantSecretKey].join('|');
const encoded = new TextEncoder().encode(hashstr);
const hashBuffer = await crypto.subtle.digest('SHA-512', encoded);
const base64 = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
form.hashDataV2.value = base64;
}
['sx', 'clientRefCode', 'amount', 'successUrl', 'failUrl', 'rnd'].forEach(function(name) {
form[name].addEventListener('input', recalcHash);
});
})();
</script>
</body>
</html>
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ödeme Başarılı</title>
<link rel="stylesheet" href="/assets/css/bootstrap-5.3.8.min.css">
</head>
<body class="bg-light">
<div class="container py-5" style="max-width: 600px;">
<div class="card shadow-sm border-success">
<div class="card-body text-center py-5">
<div class="text-success mb-3" style="font-size: 4rem;">✔</div>
<h3 class="text-success">Ödeme Başarılı</h3>
<p class="text-muted mt-3">İşlem başarıyla tamamlandı.</p>
<?php if ($_SERVER["REQUEST_METHOD"] === "POST" && !empty($_POST)): ?>
<hr>
<h6>POST Verileri</h6>
<pre class="text-start p-3 bg-body-secondary rounded small"><?php
foreach ($_POST as $key => $value) {
echo htmlspecialchars($key) . ': ' . htmlspecialchars($value) . "\n";
}
?></pre>
<?php endif; ?>
</div>
</div>
</div>
</body>
</html>
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ödeme Başarısız</title>
<link rel="stylesheet" href="/assets/css/bootstrap-5.3.8.min.css">
</head>
<body class="bg-light">
<div class="container py-5" style="max-width: 600px;">
<div class="card shadow-sm border-danger">
<div class="card-body text-center py-5">
<div class="text-danger mb-3" style="font-size: 4rem;">✘</div>
<h3 class="text-danger">Ödeme Başarısız</h3>
<p class="text-muted mt-3">İşlem gerçekleştirilemedi.</p>
<?php if ($_SERVER["REQUEST_METHOD"] === "POST" && !empty($_POST)): ?>
<hr>
<h6>POST Verileri</h6>
<pre class="text-start p-3 bg-body-secondary rounded small"><?php
foreach ($_POST as $key => $value) {
echo htmlspecialchars($key) . ': ' . htmlspecialchars($value) . "\n";
}
?></pre>
<?php endif; ?>
</div>
</div>
</div>
</body>
</html>
Common Issues and Solutions #
After payment completion, you may experience issues where the redirect to your successUrl doesn't work properly or user sessions are terminated unexpectedly. These problems are typically caused by modern browser security policies. Below are the most common technical causes and their solutions.
1. SameSite Cookie Restrictions (Most Common) #
Problem:
Modern browsers (especially Chrome and Safari) block cookies (session cookies) by default when redirects occur from different domains within an iframe. When Paynkolay posts to your successUrl, the browser treats this as a "third-party (cross-site)" request and blocks existing session cookies (such as PHPSESSID) for security reasons. This causes your application to perceive the user as "logged out" or having no active session.
Solution:
Your application must configure session cookies with the following parameters:
SameSite=None- Allows cookies to be sent in cross-site contextsSecure=true- Requires HTTPS (mandatory when usingSameSite=None)
Example configuration:
// PHP Example
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '.domain.com',
'secure' => true,
'httponly' => true,
'samesite' => 'None'
]);
session_start();// ASP.NET Core Example
services.AddSession(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});// Node.js/Express Example
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'none'
}
}));# Django Example (settings.py)
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_HTTPONLY = TrueSameSite=None to work. HTTP connections will not work with this configuration.2. X-Frame-Options Header Blocking #
Problem:
Your server or application may be configured to prevent pages from being embedded in iframes for security reasons. If your successUrl page returns an X-Frame-Options: DENY or X-Frame-Options: SAMEORIGIN header in the server response, the browser will refuse to display the page within an iframe.
Solution:
You need to either remove this restriction or allow the specific domain for your payment return page (successUrl).
Option A: Remove the header entirely
# Apache (.htaccess)
Header unset X-Frame-Options# Nginx
# Remove or comment out any add_header X-Frame-Options directivesOption B: Allow specific origin
# Apache
Header set X-Frame-Options "ALLOW-FROM https://paynkolaytest.nkolayislem.com.tr"# Nginx
add_header X-Frame-Options "ALLOW-FROM https://paynkolaytest.nkolayislem.com.tr";Option C: Use Content-Security-Policy (modern recommended approach)
# Apache
Header set Content-Security-Policy "frame-ancestors 'self' https://paynkolaytest.nkolayislem.com.tr https://paynkolay.com.tr"# Nginx
add_header Content-Security-Policy "frame-ancestors 'self' https://paynkolaytest.nkolayislem.com.tr https://paynkolay.com.tr";// ASP.NET Core (Startup.cs or Program.cs)
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy",
"frame-ancestors 'self' https://paynkolaytest.nkolayislem.com.tr https://paynkolay.com.tr");
await next();
});// PHP
header("Content-Security-Policy: frame-ancestors 'self' https://paynkolaytest.nkolayislem.com.tr https://paynkolay.com.tr");You can check your current headers using browser developer tools:
1. Open Developer Tools (F12)
2. Go to the Network tab
3. Reload your page and click on the document request
4. Look for
X-Frame-Options or Content-Security-Policy headers in the Response Headers sectionIf you see blocking headers, apply the appropriate solution above based on your server configuration.
Checklist for iframe Integration #
Before deploying iframe integration to production, ensure you have:
- Configured session cookies with
SameSite=NoneandSecure=true - Enabled HTTPS on your website (required for
SameSite=None) - Removed or configured
X-Frame-Optionsheaders to allow iframe embedding - Set appropriate
Content-Security-Policywithframe-ancestorsdirective - Tested the complete payment flow including success and failure scenarios
- Verified that user sessions persist after payment completion
- Tested across multiple browsers (Chrome, Safari, Firefox, Edge)
Technical Background #
The issues described above stem from security enhancements implemented by browser vendors to protect users from clickjacking attacks and cross-site data leakage. While these protections improve web security overall, they require careful configuration when implementing legitimate iframe-based integrations like payment gateways.
The SameSite cookie attribute was introduced to prevent Cross-Site Request Forgery (CSRF) attacks. By default, browsers now treat cookies as SameSite=Lax, which blocks them in cross-site POST requests within iframes. Setting SameSite=None explicitly opts into the previous behavior but requires the Secure flag to ensure cookies are only transmitted over encrypted connections.
Similarly, X-Frame-Options and Content-Security-Policy headers protect against clickjacking by controlling which sites can embed your pages in frames. For payment integrations, you need to selectively allow embedding from trusted payment provider domains while maintaining protection against unauthorized framing.