DOCS
Panel Giriş Başvurun

3DS

Taksit Bilgisi Paynkolay'a Ait

Bu versiyonda 3D ödeme iki adımda tamamlanır: önce taksit sorgulanır, ardından dönen EncodedValue ile ödeme başlatılır.

Adım 1 — Taksit Sorgulama

Test POST https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments

Canlı POST https://paynkolay.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments

sx, amount, cardNumber, hosturl ve iscardvalid değişkenleri Body'de form-data olarak POST edilir. Sadece BIN (kartın ilk 8 hanesi) ile sorgulamak için iscardvalid false gönderilir ya da hiç gönderilmez.

Yanıttaki PAYMENT_BANK_LIST dizisinden kullanıcının seçtiği taksit opsiyonunun EncodedValue ve INSTALLMENT değerleri alınır. Tüm alanlar ve örnek yanıt için Taksit Servisi sayfasına bakın.

Adım 2 — Ödeme

Test POST https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/Payment

Canlı POST https://paynkolay.nkolayislem.com.tr/Vpos/Payment/Payment

EncodedValue, seçilen installmentNo ve diğer değişkenler (sx, clientRefCode, successUrl, failUrl, amount, cardHolderName, month, year, cvv, cardNumber, use3D, transactionType, hosturl, rnd, hashDatav2, environment) form-data olarak POST edilir. use3D true gönderilir.

Yanıttaki BANK_REQUEST_MESSAGE alanı bankanın 3D formudur (HTML). Bu form tarayıcıya basıldığında bankanın 3D Secure penceresi açılır ve kart sahibinden 3D şifresini girmesi beklenir.

Doğrulama sonucu bankadan successUrl / failUrl adresine redirect ile döner; sipariş ancak callback'te hash doğrulaması sonrası onaylanır.

V1'den Farkı

Taksit tablosu sizde değil Paynkolay'dadır; bu yüzden Payment çağrısından önce PaymentInstallments zorunludur ve EncodedValue olmadan ödeme yapılamaz. EncodedValue tutar ve taksit bilgisini imzalı taşıdığı için istemci tarafında taksit/tutar manipülasyonunu da engeller.

İstek ve Yanıt

Akış üç adımdır: taksit sorgusunun yanıtından EncodedValue alınır, ödeme isteğinin yanıtı bankanın 3D formudur (BANK_REQUEST_MESSAGE); müşteri doğrulamayı tamamlayınca sonuç sunucunuza ayrı bir form POST olarak gelir.

1 → İstek · POST /Vpos/Payment/PaymentInstallments (form-data)
sx=SX_DEGERINIZ
amount=6.00
cardNumber=4546711234567894
hosturl=https://siteniz.com
iscardvalid=false
1 ← Yanıt · JSON (kısaltılmış)
{
  "PAYMENT_BANK_LIST": [
    {
      "INSTALLMENT": 1,
      "TRANSACTION_AMOUNT": 6.00,
      "EncodedValue": "UnBVK2tLNDYzbjZ2OE5VZ3Y3Vzl3MlRKTmVlZnlOdm1FNi9H…"
    }
  ],
  "RESPONSE_CODE": 2,
  "RESPONSE_DATA": "İşlem Başarılı."
}
2 → İstek · POST /Vpos/Payment/Payment (form-data)
EncodedValue=UnBVK2tLNDYzbjZ2OE5VZ3Y3Vzl3MlRKTmVlZnlOdm1FNi9H…
installmentNo=1
amount=6.00
sx=SX_DEGERINIZ
clientRefCode=789456|AB76
successUrl=https://siteniz.com/payment/callback
failUrl=https://siteniz.com/payment/callback
cardHolderName=Tuna Çınar
month=12
year=2026
cvv=001
cardNumber=4546711234567894
use3D=true
transactionType=SALES
hosturl=https://siteniz.com
cardHolderIP=85.100.20.15
rnd=15-09-2026 14:25:11
hashDatav2=lunnRyNfDu6pdpNjjlFrlATE…
environment=API
2 ← Yanıt · JSON (kısaltılmış)
{
  "BANK_REQUEST_MESSAGE": "<form action="…banka 3D sayfası…" method="post">…</form>"
}
3 → Sonuç · POST https://siteniz.com/payment/callback
# form POST — JSON değil
RESPONSE_CODE=2
RESPONSE_DATA=İşlem Başarılı.
REFERENCE_CODE=IKSIRPF142277040
USE_3D=true
AUTH_CODE=345344
CLIENT_REFERENCE_CODE=789456|AB76
TRANSACTION_AMOUNT=6.00
AUTHORIZATION_AMOUNT=6.00
INSTALLMENT=1
RND=1645700316156
hashDataV2=lunnRyNfDu6pdpNjjlFrlATE…

Sonuç adresinize geldiğinde siparişi onaylamadan önce hashDataV2 imzasını doğrulayın; ödeme yalnızca RESPONSE_CODE=2 ve geçerli bir AUTH_CODE ile başarılıdır — bkz. Ödeme Sonucu.

Yanıt örnekleri Taksit Servisi ve Ödeme Sonucu sayfalarındaki belgelenmiş yanıtlardan kısaltılmıştır; değerler temsilidir.

Örnek Kod

İki adımlı akışın dört dilde çalışır örneği. Taksit listesi boş dönerse ödeme adımına geçilmez. SX_DEGERINIZ ve SECRET_KEY_DEGERINIZ yerine kendi değerlerinizi yazın.

<?php
// === V0 3D FLOW: PaymentInstallments → Payment ===
// Taksit Bilgisi Paynkolay'a Ait — önce taksit sorgulanır, dönen EncodedValue ile ödeme başlatılır.
// Çalıştırma: php -S localhost:8000 example.php  →  http://localhost:8000
// Not: Canlı uç localhost'tan gelen istekleri reddeder; canlıda sunucunuzdan çalıştırın.

date_default_timezone_set('Europe/Istanbul'); // rnd Türkiye saatiyle üretilmeli

$merchantSecretKey = "SECRET_KEY_DEGERINIZ";
$sx                = "SX_DEGERINIZ";
$clientRefCode     = "789456|AB76";                          // her işlemde benzersiz olmalı (ör. "ORDER-" . uniqid())
$amount            = "6.00";
$successUrl        = "https://siteniz.com/payment/callback"; // 3D sonucu buraya döner
$failUrl           = "https://siteniz.com/payment/callback";
$hostUrl           = "https://siteniz.com";
$rnd               = date("d-m-Y H:i:s");
$customerKey       = "";                                     // kart saklanmıyorsa boş
$cardNumber        = "4546711234567894";

// Müşteri IP'si: önce proxy başlıkları, yoksa bağlantı adresi
function getClientIp(): string
{
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP'] as $header) {
        if (!empty($_SERVER[$header])) {
            $ip = trim(explode(',', $_SERVER[$header])[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                return $ip;
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
}

// form-data POST — dizi verilince istek multipart/form-data gider
function postForm(string $url, array $fields): string
{
    $curl = curl_init($url);
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $fields,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $response = curl_exec($curl);
    $error    = curl_error($curl);
    curl_close($curl);

    if ($response === false) {
        header('Content-Type: text/plain; charset=utf-8');
        exit("Bağlantı hatası: " . $error);
    }
    return $response;
}

// Hash: sx|clientRefCode|amount|successUrl|failUrl|rnd|customerKey|secretKey → SHA512 (ham bayt) → Base64
$hashstr    = "$sx|$clientRefCode|$amount|$successUrl|$failUrl|$rnd|$customerKey|$merchantSecretKey";
$hashDataV2 = base64_encode(hash("sha512", $hashstr, true));

// --- ADIM 1: Taksit sorgulama ---
$instResponse = postForm('https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments', [
    'sx'          => $sx,
    'amount'      => $amount,
    'cardNumber'  => $cardNumber,
    'hosturl'     => $hostUrl,
    'iscardvalid' => 'false',   // sadece BIN (ilk 8 hane) ile sorgu
]);

$instData = json_decode($instResponse, true);
$option   = $instData['PAYMENT_BANK_LIST'][0] ?? null;  // gerçek entegrasyonda kullanıcının seçtiği opsiyon

// PAYMENT_BANK_LIST boşsa Adım 2'ye geçilmez
if (!is_array($option) || empty($option['EncodedValue'])) {
    header('Content-Type: text/plain; charset=utf-8');
    exit("Taksit bilgisi alınamadı: " . $instResponse);
}

$encodedValue  = $option['EncodedValue'];
$installmentNo = (string)($option['INSTALLMENT'] ?? '1');

// --- ADIM 2: 3D ödeme ---
$payResponse = postForm('https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/Payment', [
    'EncodedValue'    => $encodedValue,      // ← V0'a özgü, Adım 1'den gelir
    'installmentNo'   => $installmentNo,
    'amount'          => $amount,
    'sx'              => $sx,
    'clientRefCode'   => $clientRefCode,
    'successUrl'      => $successUrl,
    'failUrl'         => $failUrl,
    'cardHolderName'  => 'Tuna Çınar',
    'month'           => '12',
    'year'            => '2026',
    'cvv'             => '001',
    'cardNumber'      => $cardNumber,
    'use3D'           => 'true',             // ← 3D akışı
    'transactionType' => 'SALES',
    'hosturl'         => $hostUrl,
    'cardHolderIP'    => getClientIp(),
    'rnd'             => $rnd,
    'hashDatav2'      => $hashDataV2,
    'environment'     => 'API',
]);

$payData = json_decode($payResponse, true);

// === 3D'ye özgü: BANK_REQUEST_MESSAGE'ı tarayıcıya bas ===
if (!empty($payData['BANK_REQUEST_MESSAGE'])) {
    header('Content-Type: text/html; charset=utf-8');
    echo $payData['BANK_REQUEST_MESSAGE'];   // banka ACS'ye auto-submit olur
    exit;
}

header('Content-Type: text/plain; charset=utf-8');
echo "3D formu alınamadı: " . $payResponse;
// Sonuç successUrl/failUrl callback'inde — hash doğrulaması orada zorunlu
// === V0 3D FLOW: PaymentInstallments → Payment (.NET 8 minimal API) ===
// Taksit Bilgisi Paynkolay'a Ait — önce taksit sorgulanır, dönen EncodedValue ile ödeme başlatılır.
// Kurulum: dotnet new web -n PaynkolayV0ThreeD → bu dosyayı Program.cs ile değiştirin → dotnet run
// Test: tarayıcıda http://localhost:<port>/odeme (port konsolda yazar)
// Not: Canlı uç localhost'tan gelen istekleri reddeder; canlıda sunucunuzdan çalıştırın.
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
var app = builder.Build();

// Kimlik bilgileri (size verilen değerler)
const string Sx = "SX_DEGERINIZ";
const string MerchantSecretKey = "SECRET_KEY_DEGERINIZ";

// Test uçları — canlıda alan adı paynkolay.nkolayislem.com.tr olur
const string InstallmentsUrl = "https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments";
const string PaymentUrl = "https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/Payment";

app.MapGet("/odeme", async (HttpContext ctx, IHttpClientFactory httpFactory) =>
{
    var clientRefCode = "789456|AB76";                      // her işlemde benzersiz olmalı (ör. "ORDER-" + Guid)
    var amount = "6.00";
    var successUrl = "https://siteniz.com/payment/callback"; // 3D sonucu buraya döner
    var failUrl = "https://siteniz.com/payment/callback";
    var hostUrl = "https://siteniz.com";
    var customerKey = "";                                   // kart saklanmıyorsa boş
    var cardNumber = "4546711234567894";

    // rnd: Türkiye saatiyle (UTC+3) dd-MM-yyyy HH:mm:ss
    var rnd = DateTime.UtcNow.AddHours(3).ToString("dd-MM-yyyy HH:mm:ss", CultureInfo.InvariantCulture);

    // Hash: sx|clientRefCode|amount|successUrl|failUrl|rnd|customerKey|secretKey → SHA512 (ham bayt) → Base64
    var hashStr = string.Join("|", Sx, clientRefCode, amount, successUrl, failUrl, rnd, customerKey, MerchantSecretKey);
    var hashDatav2 = Convert.ToBase64String(SHA512.HashData(Encoding.UTF8.GetBytes(hashStr)));

    var http = httpFactory.CreateClient();
    http.Timeout = TimeSpan.FromSeconds(30);

    try
    {
        // --- ADIM 1: Taksit sorgulama ---
        var instBody = await PostFormAsync(http, InstallmentsUrl, new()
        {
            ["sx"] = Sx,
            ["amount"] = amount,
            ["cardNumber"] = cardNumber,
            ["hosturl"] = hostUrl,
            ["iscardvalid"] = "false",   // sadece BIN (ilk 8 hane) ile sorgu
        });

        // PAYMENT_BANK_LIST boşsa Adım 2'ye geçilmez
        var option = FirstInstallmentOption(instBody); // gerçek entegrasyonda kullanıcının seçtiği opsiyon
        if (option is null)
        {
            return PlainText($"Taksit bilgisi alınamadı: {instBody}", 400);
        }
        var (encodedValue, installmentNo) = option.Value;

        // --- ADIM 2: 3D ödeme ---
        var payBody = await PostFormAsync(http, PaymentUrl, new()
        {
            ["EncodedValue"] = encodedValue,   // ← V0'a özgü, Adım 1'den gelir
            ["installmentNo"] = installmentNo,
            ["amount"] = amount,
            ["sx"] = Sx,
            ["clientRefCode"] = clientRefCode,
            ["successUrl"] = successUrl,
            ["failUrl"] = failUrl,
            ["cardHolderName"] = "Tuna Çınar",
            ["month"] = "12",
            ["year"] = "2026",
            ["cvv"] = "001",
            ["cardNumber"] = cardNumber,
            ["use3D"] = "true",                // ← 3D akışı
            ["transactionType"] = "SALES",
            ["hosturl"] = hostUrl,
            ["cardHolderIP"] = GetClientIp(ctx),
            ["rnd"] = rnd,
            ["hashDatav2"] = hashDatav2,
            ["environment"] = "API",
        });

        // === 3D'ye özgü: BANK_REQUEST_MESSAGE'ı tarayıcıya bas (banka ACS'ye auto-submit olur) ===
        var bankForm = ReadString(payBody, "BANK_REQUEST_MESSAGE");
        if (!string.IsNullOrEmpty(bankForm))
        {
            return Results.Content(bankForm, "text/html; charset=utf-8");
        }

        // Sonuç successUrl/failUrl callback'inde — hash doğrulaması orada zorunlu
        return PlainText($"3D formu alınamadı: {payBody}", 400);
    }
    catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
    {
        return PlainText($"Bağlantı hatası: {ex.Message}", 502);
    }
});

app.Run();

static IResult PlainText(string message, int statusCode) =>
    Results.Text(message, "text/plain; charset=utf-8", statusCode: statusCode);

// form-data POST (multipart/form-data)
static async Task<string> PostFormAsync(HttpClient http, string url, Dictionary<string, string> fields)
{
    using var form = new MultipartFormDataContent();
    foreach (var (key, value) in fields)
    {
        form.Add(new StringContent(value), key);
    }
    using var response = await http.PostAsync(url, form);
    return await response.Content.ReadAsStringAsync();
}

// PAYMENT_BANK_LIST[0] → (EncodedValue, INSTALLMENT); liste boşsa null
static (string EncodedValue, string Installment)? FirstInstallmentOption(string body)
{
    try
    {
        using var json = JsonDocument.Parse(body);
        var root = json.RootElement;
        if (root.ValueKind != JsonValueKind.Object
            || !root.TryGetProperty("PAYMENT_BANK_LIST", out var list)
            || list.ValueKind != JsonValueKind.Array
            || list.GetArrayLength() == 0)
        {
            return null;
        }

        var first = list[0];
        if (first.ValueKind != JsonValueKind.Object || !first.TryGetProperty("EncodedValue", out var encoded))
        {
            return null;
        }

        var encodedValue = encoded.ToString();
        if (string.IsNullOrEmpty(encodedValue))
        {
            return null;
        }

        var installment = first.TryGetProperty("INSTALLMENT", out var inst) ? inst.ToString() : "1";
        return (encodedValue, installment);
    }
    catch (JsonException)
    {
        return null;
    }
}

// JSON yanıttan tek bir alanı metin olarak okur; yoksa null
static string? ReadString(string body, string property)
{
    try
    {
        using var json = JsonDocument.Parse(body);
        return json.RootElement.ValueKind == JsonValueKind.Object
            && json.RootElement.TryGetProperty(property, out var value)
            ? value.ToString()
            : null;
    }
    catch (JsonException)
    {
        return null;
    }
}

// Müşteri IP'si: önce proxy başlıkları, yoksa bağlantı adresi
static string GetClientIp(HttpContext ctx)
{
    foreach (var header in new[] { "CF-Connecting-IP", "X-Forwarded-For", "X-Real-IP" })
    {
        var value = ctx.Request.Headers[header].ToString();
        if (string.IsNullOrWhiteSpace(value)) continue;

        var ip = value.Split(',')[0].Trim();
        if (IPAddress.TryParse(ip, out _)) return ip;
    }

    var remote = ctx.Connection.RemoteIpAddress;
    if (remote is null) return "127.0.0.1";
    return (remote.IsIPv4MappedToIPv6 ? remote.MapToIPv4() : remote).ToString();
}
# === V0 3D FLOW: PaymentInstallments → Payment (Python + requests) ===
# Taksit Bilgisi Paynkolay'a Ait — önce taksit sorgulanır, dönen EncodedValue ile ödeme başlatılır.
# Kurulum: pip install requests
# Çalıştırma: python3 example.py  →  http://localhost:8000/odeme
# Not: Canlı uç localhost'tan gelen istekleri reddeder; canlıda sunucunuzdan çalıştırın.
import base64
import hashlib
import ipaddress
import json
from datetime import datetime, timedelta, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer

import requests

# Kimlik bilgileri (size verilen değerler)
SX = "SX_DEGERINIZ"
MERCHANT_SECRET_KEY = "SECRET_KEY_DEGERINIZ"

# Test uçları — canlıda alan adı paynkolay.nkolayislem.com.tr olur
INSTALLMENTS_URL = "https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments"
PAYMENT_URL = "https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/Payment"


def client_ip(handler):
    """Müşteri IP'si: önce proxy başlıkları, yoksa bağlantı adresi."""
    for header in ("CF-Connecting-IP", "X-Forwarded-For", "X-Real-IP"):
        value = handler.headers.get(header)
        if value:
            ip = value.split(",")[0].strip()
            try:
                ipaddress.ip_address(ip)
                return ip
            except ValueError:
                pass
    return handler.client_address[0]


def post_form(url, fields):
    """form-data POST — files=(None, değer) biçimi isteği multipart/form-data gönderir."""
    response = requests.post(url, files={k: (None, v) for k, v in fields.items()}, timeout=30)
    return response.text


def parse_json(text):
    try:
        data = json.loads(text)
        return data if isinstance(data, dict) else None
    except ValueError:
        return None


def pay(card_holder_ip):
    """(HTTP durum, içerik tipi, gövde) döner."""
    client_ref_code = "789456|AB76"  # her işlemde benzersiz olmalı (ör. "ORDER-" + uuid)
    amount = "6.00"
    success_url = "https://siteniz.com/payment/callback"  # 3D sonucu buraya döner
    fail_url = "https://siteniz.com/payment/callback"
    host_url = "https://siteniz.com"
    customer_key = ""  # kart saklanmıyorsa boş
    card_number = "4546711234567894"

    # rnd: Türkiye saatiyle (UTC+3) dd-MM-yyyy HH:mm:ss
    rnd = datetime.now(timezone(timedelta(hours=3))).strftime("%d-%m-%Y %H:%M:%S")

    # Hash: sx|clientRefCode|amount|successUrl|failUrl|rnd|customerKey|secretKey → SHA512 (ham bayt) → Base64
    hash_str = "|".join([SX, client_ref_code, amount, success_url, fail_url, rnd, customer_key, MERCHANT_SECRET_KEY])
    hash_datav2 = base64.b64encode(hashlib.sha512(hash_str.encode("utf-8")).digest()).decode("ascii")

    # --- ADIM 1: Taksit sorgulama ---
    inst_body = post_form(INSTALLMENTS_URL, {
        "sx": SX,
        "amount": amount,
        "cardNumber": card_number,
        "hosturl": host_url,
        "iscardvalid": "false",  # sadece BIN (ilk 8 hane) ile sorgu
    })

    inst_data = parse_json(inst_body) or {}
    bank_list = inst_data.get("PAYMENT_BANK_LIST")
    option = bank_list[0] if isinstance(bank_list, list) and bank_list else None  # gerçek entegrasyonda kullanıcının seçtiği opsiyon

    # PAYMENT_BANK_LIST boşsa Adım 2'ye geçilmez
    if not isinstance(option, dict) or not option.get("EncodedValue"):
        return 400, "text/plain", "Taksit bilgisi alınamadı: " + inst_body

    encoded_value = option["EncodedValue"]
    installment_no = str(option.get("INSTALLMENT", "1"))

    # --- ADIM 2: 3D ödeme ---
    pay_body = post_form(PAYMENT_URL, {
        "EncodedValue": encoded_value,  # ← V0'a özgü, Adım 1'den gelir
        "installmentNo": installment_no,
        "amount": amount,
        "sx": SX,
        "clientRefCode": client_ref_code,
        "successUrl": success_url,
        "failUrl": fail_url,
        "cardHolderName": "Tuna Çınar",
        "month": "12",
        "year": "2026",
        "cvv": "001",
        "cardNumber": card_number,
        "use3D": "true",  # ← 3D akışı
        "transactionType": "SALES",
        "hosturl": host_url,
        "cardHolderIP": card_holder_ip,
        "rnd": rnd,
        "hashDatav2": hash_datav2,
        "environment": "API",
    })

    # === 3D'ye özgü: BANK_REQUEST_MESSAGE'ı tarayıcıya bas (banka ACS'ye auto-submit olur) ===
    bank_form = (parse_json(pay_body) or {}).get("BANK_REQUEST_MESSAGE")
    if bank_form:
        return 200, "text/html", bank_form

    # Sonuç successUrl/failUrl callback'inde — hash doğrulaması orada zorunlu
    return 400, "text/plain", "3D formu alınamadı: " + pay_body


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/odeme":
            return self.reply(404, "text/plain", "Ödeme için /odeme adresini açın.")
        try:
            self.reply(*pay(client_ip(self)))
        except requests.RequestException as exc:
            self.reply(502, "text/plain", "Bağlantı hatası: {}".format(exc))

    def reply(self, status, content_type, text):
        body = text.encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", content_type + "; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


if __name__ == "__main__":
    print("http://localhost:8000/odeme")
    HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
// === V0 3D FLOW: PaymentInstallments → Payment (Node.js + axios + form-data) ===
// Taksit Bilgisi Paynkolay'a Ait — önce taksit sorgulanır, dönen EncodedValue ile ödeme başlatılır.
// Kurulum: npm install axios form-data
// Çalıştırma: node example.js  →  http://localhost:8000/odeme
// Not: Canlı uç localhost'tan gelen istekleri reddeder; canlıda sunucunuzdan çalıştırın.
const http = require('http');
const net = require('net');
const crypto = require('crypto');
const axios = require('axios');
const FormData = require('form-data');

// Kimlik bilgileri (size verilen değerler)
const SX = 'SX_DEGERINIZ';
const MERCHANT_SECRET_KEY = 'SECRET_KEY_DEGERINIZ';

// Test uçları — canlıda alan adı paynkolay.nkolayislem.com.tr olur
const INSTALLMENTS_URL = 'https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/PaymentInstallments';
const PAYMENT_URL = 'https://paynkolaytest.nkolayislem.com.tr/Vpos/Payment/Payment';

// Müşteri IP'si: önce proxy başlıkları, yoksa bağlantı adresi
function clientIp(req) {
  for (const header of ['cf-connecting-ip', 'x-forwarded-for', 'x-real-ip']) {
    const value = req.headers[header];
    if (value) {
      const ip = String(value).split(',')[0].trim();
      if (net.isIP(ip)) return ip;
    }
  }
  return (req.socket.remoteAddress || '127.0.0.1').replace(/^::ffff:/, '');
}

// rnd: Türkiye saatiyle (UTC+3) dd-MM-yyyy HH:mm:ss
function turkeyTime() {
  const d = new Date(Date.now() + 3 * 60 * 60 * 1000);
  const p = (n) => String(n).padStart(2, '0');
  return `${p(d.getUTCDate())}-${p(d.getUTCMonth() + 1)}-${d.getUTCFullYear()} ` +
    `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}`;
}

// form-data POST (multipart/form-data) — gövdeyi metin olarak döner
async function postForm(url, fields) {
  const form = new FormData();
  for (const [key, value] of Object.entries(fields)) form.append(key, value);

  const response = await axios.post(url, form, {
    headers: form.getHeaders(),
    timeout: 30000,
    responseType: 'text',
    validateStatus: () => true, // HTTP durum kodundan bağımsız gövdeyi okuyalım
  });
  return response.data;
}

function parseJson(text) {
  try {
    const data = JSON.parse(text);
    return data && typeof data === 'object' ? data : {};
  } catch {
    return {};
  }
}

// { status, type, body } döner
async function pay(cardHolderIP) {
  const clientRefCode = '789456|AB76';                      // her işlemde benzersiz olmalı (ör. 'ORDER-' + Date.now())
  const amount = '6.00';
  const successUrl = 'https://siteniz.com/payment/callback'; // 3D sonucu buraya döner
  const failUrl = 'https://siteniz.com/payment/callback';
  const hostUrl = 'https://siteniz.com';
  const customerKey = '';                                   // kart saklanmıyorsa boş
  const cardNumber = '4546711234567894';
  const rnd = turkeyTime();

  // Hash: sx|clientRefCode|amount|successUrl|failUrl|rnd|customerKey|secretKey → SHA512 (ham bayt) → Base64
  const hashStr = [SX, clientRefCode, amount, successUrl, failUrl, rnd, customerKey, MERCHANT_SECRET_KEY].join('|');
  const hashDatav2 = crypto.createHash('sha512').update(hashStr, 'utf8').digest('base64');

  // --- ADIM 1: Taksit sorgulama ---
  const instBody = await postForm(INSTALLMENTS_URL, {
    sx: SX,
    amount,
    cardNumber,
    hosturl: hostUrl,
    iscardvalid: 'false', // sadece BIN (ilk 8 hane) ile sorgu
  });

  const bankList = parseJson(instBody).PAYMENT_BANK_LIST;
  const option = Array.isArray(bankList) ? bankList[0] : undefined; // gerçek entegrasyonda kullanıcının seçtiği opsiyon

  // PAYMENT_BANK_LIST boşsa Adım 2'ye geçilmez
  if (!option || !option.EncodedValue) {
    return { status: 400, type: 'text/plain', body: `Taksit bilgisi alınamadı: ${instBody}` };
  }

  const encodedValue = option.EncodedValue;
  const installmentNo = String(option.INSTALLMENT ?? '1');

  // --- ADIM 2: 3D ödeme ---
  const payBody = await postForm(PAYMENT_URL, {
    EncodedValue: encodedValue,   // ← V0'a özgü, Adım 1'den gelir
    installmentNo,
    amount,
    sx: SX,
    clientRefCode,
    successUrl,
    failUrl,
    cardHolderName: 'Tuna Çınar',
    month: '12',
    year: '2026',
    cvv: '001',
    cardNumber,
    use3D: 'true',                // ← 3D akışı
    transactionType: 'SALES',
    hosturl: hostUrl,
    cardHolderIP,
    rnd,
    hashDatav2,
    environment: 'API',
  });

  // === 3D'ye özgü: BANK_REQUEST_MESSAGE'ı tarayıcıya bas (banka ACS'ye auto-submit olur) ===
  const bankForm = parseJson(payBody).BANK_REQUEST_MESSAGE;
  if (bankForm) {
    return { status: 200, type: 'text/html', body: bankForm };
  }

  // Sonuç successUrl/failUrl callback'inde — hash doğrulaması orada zorunlu
  return { status: 400, type: 'text/plain', body: `3D formu alınamadı: ${payBody}` };
}

http.createServer(async (req, res) => {
  const reply = (status, type, text) => {
    res.writeHead(status, { 'Content-Type': `${type}; charset=utf-8` });
    res.end(text);
  };

  if (req.url !== '/odeme') return reply(404, 'text/plain', 'Ödeme için /odeme adresini açın.');

  try {
    const { status, type, body } = await pay(clientIp(req));
    reply(status, type, body);
  } catch (err) {
    reply(502, 'text/plain', `Bağlantı hatası: ${err.message}`);
  }
}).listen(8000, () => console.log('http://localhost:8000/odeme'));

Son güncelleme: 15 Eylül 2026

v1 · Versiyonlar