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.
sx=SX_DEGERINIZ
amount=6.00
cardNumber=4546711234567894
hosturl=https://siteniz.com
iscardvalid=false{
"PAYMENT_BANK_LIST": [
{
"INSTALLMENT": 1,
"TRANSACTION_AMOUNT": 6.00,
"EncodedValue": "UnBVK2tLNDYzbjZ2OE5VZ3Y3Vzl3MlRKTmVlZnlOdm1FNi9H…"
}
],
"RESPONSE_CODE": 2,
"RESPONSE_DATA": "İşlem Başarılı."
}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{
"BANK_REQUEST_MESSAGE": "<form action="…banka 3D sayfası…" method="post">…</form>"
}# 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