İçeriğe geç
Dokümantasyon

Direct API

Direct API’de kart formu sitenizin bir parçasıdır ve kart bilgilerini Payniko’ya sunucunuz iletir. Müşterinin tarayıcısı Payniko’ya yalnızca 3-D Secure doğrulaması ve hosted BIN’ler için yönlendirilir. Kart formunun kendi sayfanızda yer alması gerekiyorsa Direct API’yi kullanın.

Entegrasyon adımları

  1. BIN sorgulama

    Müşteri ilk altı haneyi girdiğinde GET /api/payments/bin/{bin6} çağrısını yapın. payment_mode, kart bilgilerinin tamamını kendi formunuzda mı alacağınızı yoksa müşteriyi ödeme sayfasına mı yönlendireceğinizi belirtir.

  2. Ödemeyi oluşturma

    POST /api/payments/direct çağrısını kart bilgileriyle veya hosted BIN için yalnızca bin6 ile yapın.

  3. 3-D Secure adımını tamamlama

    Durum requires_action ise tarayıcıyı next_action.url adresine yönlendirin. Tarayıcı ardından imzalı parametrelerle success_url veya fail_url adresinize geri döner.

  4. Sonucu doğrulama

    payment.succeeded veya payment.failed webhook’unu bekleyin ya da GET /api/payments/{payment_id} çağrısını yapın. Siparişi yalnızca succeeded durumunda teslim edin.

Ödeme akışı

Your server                          Payniko                        Payer's browser
 1  GET /api/payments/bin/{bin6} ───►  payment_mode: direct | hosted
 2  POST /api/payments/direct  ─────►  200  requires_action + next_action.url
                                            (or processing / succeeded / failed)
 3  redirect to next_action.url ──────────────────────────────────►  3-D Secure page
                                       ───────────────────────────►  success_url / fail_url
 4  webhook  ◄───────────────────────  payment.succeeded / payment.failed
    GET /api/payments/{payment_id} ──►  current status

Ön koşullar

  • Direct API hesap bazında etkinleştirilir. Bu erişim olmadan yapılan istekler 403 0055502 ile reddedilir.
  • Anahtarlar, başlıklar ve imzalama Hosted API ile aynıdır; bkz. Kimlik doğrulama. Örneklerde istek yardımcı fonksiyonları kullanılır.
GET/api/payments/bin/{bin6}

1. BIN sorgulama

BIN sorgusu, ödeme oluşturulmadan önce ödemenin kendi formunuzda alınıp alınamayacağını (direct) veya Payniko ödeme sayfasında tamamlanması gerektiğini (hosted) belirtir. Sorgu, karttan herhangi bir tahsilat yapılmasına yol açmaz.

Parametreler

AlanTürZorunluAçıklamaÖrnek
bin6string · pathZorunluKart numarasının ilk altı hanesi.540061
amountstring · queryZorunluİki ondalık basamaklı tutar, örneğin 100.00. Hesabınızın tutar sınırlarına göre kontrol edilir.100.00
currencystring · queryİsteğe bağlıUSD, EUR, AED veya TRY. Varsayılan değer sistemin ana para birimidir. Ödemeyi oluştururken aynı para birimini kullanın.USD

Diğer sorgu parametreleri reddedilir. Sorgu dizesi imzaya dahil değildir.

./payniko.sh GET '/api/payments/bin/540061?amount=100.00&currency=USD'
import {api} from './payniko.mjs';

const lookup = await api('GET', '/api/payments/bin/540061?amount=100.00&currency=USD');
console.log(lookup);
<?php
require __DIR__ . '/payniko.php';

$lookup = payniko_api('GET', '/api/payments/bin/540061?amount=100.00&currency=USD');
print_r($lookup);
from payniko import api

lookup = api("GET", "/api/payments/bin/540061?amount=100.00&currency=USD")
print(lookup)

Yanıt · Örnek

{
  "data": {
    "bin6": "540061",
    "bank": "Test Bank",
    "network": "mastercard",
    "card_type": "credit",
    "card_program": null,
    "country": "TR",
    "domestic": true,
    "payment_mode": "hosted"
  }
}

Yanıt · 200

Aşağıdaki alanları içeren { "data": ... } nesnesini döndürür:

AlanTürAçıklama
bin6stringGönderilen altı hane.
bankstring | nullKartı çıkaran banka (biliniyorsa).
networkstring | nullKart markası (biliniyorsa).
visamastercardamextroy
card_typestring | nullKart türü (biliniyorsa).
creditdebitprepaid
card_programstring | nullKart programı. Şu anda her zaman null.
countrystring | nullKartı çıkaran ülke (ISO 3166-1 alpha-2; biliniyorsa).
domesticbooleanYurt içi kartlarda true; diğer durumlarda veya bilinmiyorsa false.
payment_modestring | nulldirect: kart bilgilerinin tamamını alın. hosted: müşteriyi ödeme sayfasına yönlendirin. null dahil diğer tüm değerler: ödeme oluşturmayın.
directhosted

Yanıtın değerlendirilmesi

  • direct: kart bilgilerinin tamamını alın ve ödemeyi card ile oluşturun.
  • hosted: ödemeyi yalnızca bin6 ile oluşturun. Müşteri kart bilgilerini Payniko ödeme sayfasında girer.
  • Diğer payment_mode değerleri veya 409 kart hataları (0055230, 0055231, 0055233): ödeme oluşturmayın; bu bir banka reddi olmadığından müşteriden nötr bir mesajla farklı bir kart isteyin. 422 0055235 bir tutar sorununa işaret eder. 429 veya 5xx durumunda aynı sorguyu daha sonra yineleyin.

Örnek

import {api} from './payniko.mjs';

let mode = null;
try {
  const lookup = await api('GET', '/api/payments/bin/411111?amount=100.00&currency=USD');
  mode = lookup.data.payment_mode;
} catch (error) {
  // No answer, 429 or 5xx: temporary. Try the same lookup again later.
  if (error.status === undefined || error.status === 429 || error.status >= 500) throw error;
  // Other 4xx: error.problem.code says why, for example 0055233.
}
if (mode !== 'direct' && mode !== 'hosted') {
  // Also null: do not create a payment. Ask for another card with a neutral message.
}

Olası hatalar

POST/api/payments/directIdempotency-Key zorunlu

2. Ödeme oluşturma

Aynı istek yeniden gönderildiğinde de dahil olmak üzere her zaman güncel ödemeyle birlikte 200 döndürür. Kartın reddedilmesi bir HTTP hatası değildir; yanıt status: failed içeren 200 olur.

İstek gövdesi

AlanTürZorunluAçıklamaÖrnek
merchant_order_idstringZorunluSipariş referansınız: A-Z a-z 0-9 . _ - karakterlerinden oluşan 1–64 karakter. Her referans ve ortam için yalnızca bir ödeme oluşturulabilir; benzersizlik denetiminde büyük/küçük harf ayrımı yapılmaz.ORDER-2026-002
amountstringZorunluİki ondalık basamaklı tutar, örneğin "100.00"; hesabınızın tutar sınırları içinde olmalıdır."100.00"
currencystringİsteğe bağlıVarsayılan değer sistemin ana para birimidir. BIN sorgusunda kullandığınız para birimini kullanın.
USDEURAEDTRY
USD
webhook_urlstring · uriZorunluBu ödemenin webhook'larını alan, 443 portunda herkese açık HTTPS adresi; en fazla 500 karakter. Yerel adresler sandbox dahil kabul edilmez; alan adı istek anında herkese açık bir IP adresine çözülmelidir, aksi halde 422 9000008.https://merchant.example/webhooks/payments
success_urlstring · uriZorunluÖdeme başarılı olduğunda tarayıcının yönlendirileceği adres. HTTPS, en fazla 2048 karakter; kullanıcı bilgisi (user info) ve parça (fragment) içermemelidir. Sandbox ortamı http://localhost, 127.0.0.1 ve [::1] adreslerini de kabul eder.https://merchant.example/payment/success
fail_urlstring · uriZorunluDiğer tüm durumlarda tarayıcının yönlendirileceği adres. success_url için geçerli kurallar burada da uygulanır.https://merchant.example/payment/failure
customerobjectZorunluÖdeyenin iletişim bilgileri; ödeme sonucu, makbuz ve bankanın doğrulama adımları için kullanılır. Yalnızca email ve phone kabul edilir; customer içindeki başka bir alan 422 1000001 ile reddedilir.{ … }
customer.emailstringZorunluÖdeyenin gerçek e-posta adresi: RFC 5322, en fazla 254 karakter, noktalı alan adı. Küçük harfe çevrilerek saklanır.[email protected]
customer.phonestringZorunluÖdeyenin gerçek telefon numarası, uluslararası biçimde (+ ülke kodu, E.164), örneğin +37061234567. Türkiye'ye özgü kural: +90 ve 10 haneli ulusal numara (5xx cep, 2xx, 3xx, 4xx sabit, 8xx özel). Ülke kodu verilmeyen numara Türkiye numarası (+90) sayılır.+37061234567
cardobjectKoşulluMüşterinin girdiği kart bilgileri (aşağıdaki tabloya bakın). card veya bin6 alanlarından yalnızca birini gönderin.{ … }
bin6stringKoşulluYalnızca hosted BIN için: kart numarasının ilk altı hanesi. Müşteri kart bilgilerini ödeme sayfasında girer. direct BIN için yalnızca bin6 içeren bir istek 409 0055230 yanıtını alır.540061
card.numberstringZorunluBoşluksuz 13–19 rakam, geçerli bir kontrol basamağıyla (American Express için 15 rakam).4355090000044242
card.holder_namestringZorunluKart üzerindeki ad, 2–120 karakter.JONAS KAZLAUSKAS
card.expiry_monthstringZorunluİki hane, 01 ile 12 arasında.12
card.expiry_yearstringZorunluİki hane, örneğin 30. Kartın süresi dolmuş olmamalı ve yıl en fazla 30 yıl ileride olabilir.30
card.cvvstringZorunluTam olarak 3 hane; American Express için 4 hane. Asla saklamayın veya günlüğe yazmayın.123

Tüm kart değerleri dize türündedir. card içindeki bilinmeyen alanlar reddedilir.

hosted BIN için ödeme sayfası ödeyenden e-posta adresini ve telefon numarasını yeniden istemez. Gizlilik gereği customer ödeme nesnesinde ve webhook'larda döndürülmez.

AlanNedenerrors içindeki mesaj
customer.emailBiçim geçersiz veya 254 karakterden uzun.customer.email must be a valid e-mail address (max 254 characters).
customer.emailYer tutucu, örnek veya tek kullanımlık adres.customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted.
customer.phoneUluslararası biçimde geçerli bir numara değil.customer.phone must be a valid phone number in international format (e.g. +37061234567).
customer.phoneYinelenen veya ardışık rakam deseni.customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted.
customer.phoneTürkiye numarası +90 ve 2, 3, 4, 5 veya 8 ile başlayan 10 hane biçiminde değil.customer.phone for Türkiye must be +90 followed by a 10-digit number starting with 2, 3, 4, 5 or 8 (e.g. +905321234567).

HTTP 422 · 1000001

{
  "type": "https://payniko.com/problems/1000001",
  "title": "Validation failed",
  "status": 422,
  "detail": "The submitted fields are not valid.",
  "code": "1000001",
  "correlation_id": "4c7a1e9d-2b3f-4d85-9a6e-0f1b2c3d4e5f",
  "errors": {
    "customer.email": [
      "customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted."
    ],
    "customer.phone": [
      "customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted."
    ]
  }
}

İstek customer yüzünden reddedilirse nedeni ödeyene açıkça gösterin (örneğin: kendi e-posta adresinizi veya telefon numaranızı girin) ve ödemeyi yeniden oluşturmadan önce alanı düzeltmesine izin verin. Direct API errors içindeki mesajları çevrilmiş olarak döndürür; tablo İngilizce metni gösterir.

Payniko ödeme linki sayfaları, ödeyenin orada girdiği e-posta adresi ve telefon numarasına aynı kuralları uygular ve ret nedenini ödeyene gösterir.

Kart bilgileriyle · Örnek

{
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}
KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/payments/direct' '{
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const payment = await api('POST', '/api/payments/direct', {
  "merchant_order_id": "ORDER-2026-002",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "card": {
    "number": "4355090000044242",
    "holder_name": "JONAS KAZLAUSKAS",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}, key);
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$payment = payniko_api('POST', '/api/payments/direct', [
    "merchant_order_id" => 'ORDER-2026-002',
    "amount" => '100.00',
    "currency" => 'USD',
    "webhook_url" => 'https://merchant.example/webhooks/payments',
    "success_url" => 'https://merchant.example/payment/success',
    "fail_url" => 'https://merchant.example/payment/failure',
    "customer" => [
        "email" => '[email protected]',
        "phone" => '+37061234567',
    ],
    "card" => [
        "number" => '4355090000044242',
        "holder_name" => 'JONAS KAZLAUSKAS',
        "expiry_month" => '12',
        "expiry_year" => '30',
        "cvv" => '123',
    ],
], $key);
print_r($payment);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
payment = api("POST", "/api/payments/direct", {
    "merchant_order_id": "ORDER-2026-002",
    "amount": "100.00",
    "currency": "USD",
    "webhook_url": "https://merchant.example/webhooks/payments",
    "success_url": "https://merchant.example/payment/success",
    "fail_url": "https://merchant.example/payment/failure",
    "customer": {
        "email": "[email protected]",
        "phone": "+37061234567"
    },
    "card": {
        "number": "4355090000044242",
        "holder_name": "JONAS KAZLAUSKAS",
        "expiry_month": "12",
        "expiry_year": "30",
        "cvv": "123"
    }
}, key)
print(payment)

Yanıt · Örnek

{
  "data": {
    "payment_id": "3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35",
    "merchant_order_id": "ORDER-2026-002",
    "flow": "direct",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/example-authentication-step",
      "expires_at": "2026-11-01T12:30:00.000Z"
    },
    "card": {
      "bin6": "435509",
      "last_four": "4242",
      "network": "visa",
      "bank": "Test Bank"
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-01T12:30:00.000Z",
    "completed_at": null
  }
}

Hosted BIN için bin6 ile · Örnek

{
  "merchant_order_id": "ORDER-2026-003",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  },
  "bin6": "540061"
}

Yanıtın yorumlanması

DurumÖnerilen işlem
requires_actionTarayıcıyı next_action.url adresine yönlendirin (3-D Secure veya hosted BIN için ödeme sayfası).
processingWebhook’u bekleyin ve yeni bir ödeme oluşturmayın.
succeededSiparişi teslim edin.
failedfailure.message değerini gösterin ve müşteriden farklı bir kart isteyin.
  • Payniko bankayla iletişim kurarken çağrı birkaç saniye sürebilir. En az 60 saniyelik bir zaman aşımı kullanın ve zaman aşımından sonra isteği aynı Idempotency-Key ve gövdeyle yeniden gönderin. İlk istek hâlâ çalışıyorken (en fazla 3 dakika) yeniden gönderim metadata.retry_class: retry_same_request ile 409 0055503 döndürür: birkaç saniye bekleyip aynı isteği yeniden gönderin.
  • hosted BIN söz konusu olduğunda gönderilen card bilgileri dikkate alınmaz ve müşteri işleme ödeme sayfasında devam eder.
  • merchant_order_id için zaten bir ödeme mevcutsa veya aynı Idempotency-Key farklı bir istek gövdesiyle gönderilirse istek 409 0055503 ile reddedilir.
  • Geçersiz kart bilgileri 409 0055234 ile reddedilir. İlgili alan metadata.developer_detail ile belirtilir: card_number_invalid, card_expiry_invalid veya card_cvv_invalid. Müşteriden kart bilgilerini yeniden girmesini isteyin.
  • Eksik veya geçersiz iletişim bilgileri 422 1000001 ile reddedilir; errors içinde customer, customer.email veya customer.phone belirtilir.

3. 3-D Secure ve tarayıcı

Durum requires_action olduğunda müşterinin tarayıcısı next_action.url adresini açmalıdır; bu adreste banka 3-D Secure doğrulamasını gerçekleştirir veya müşteri işleme ödeme sayfasında devam eder.

  • Sayfanın tamamını next_action.url adresine yönlendirin veya adresi yeni bir sekmede açın; her iki yöntem de her zaman desteklenir.
  • Sayfa kart bilgilerini yeniden istemez; yalnızca bankanın gerektirdiği tarayıcı verilerini toplar.
  • Her zaman en güncel next_action.url adresini, next_action.expires_at zamanından (3-D Secure adımının bitişi) önce kullanın. Önceki bir URL’yi hiçbir zaman yeniden açmayın.
  • Ardından tarayıcı imzalı parametrelerle success_url adresine (yalnızca succeeded durumunda) veya fail_url adresine yönlendirilir (bkz. Dönüş URL’si). Bu adımdan sonra ödemeyi sorgulayın.

Çerçeve içinde gösterim

3-D Secure sayfası bir çerçeve içinde gösterilemez; sayfanın tamamını yönlendirin veya yeni bir sekme açın. Yalnızca hosted BIN’e ait ödeme sayfası, yalnızca canlı ortam anahtarlarıyla ve yalnızca ödemenin success_url adresiyle birebir aynı kaynak adres (origin) üzerinde çerçeve içinde gösterilebilir. Hosted API ödeme sayfası hiçbir zaman çerçeve içinde gösterilemez.

hosted BIN’e ait ödeme sayfası, ödeme hizmeti sağlayıcısının kendi güvenli ödeme ve 3-D Secure ekranını içerir ve kendi ödeme formunuzun içinde iframe olarak gösterilebilir.

hosted BIN’e ait ödeme sayfasını çerçeve içinde gösterirseniz, sayfanın pencerenize gönderdiği mesajlar yalnızca ödemeyi kontrol etmeniz gerektiğine dair bir sinyaldir. Bu mesajları yalnızca next_action.url adresinin kaynak adresinden (origin) ve kendi çerçevenizden kabul edin, içeriklerini dikkate almayın ve ödemeyi sunucunuz üzerinden sorgulayın.

Tarayıcı

// Only if you show the payment page of a hosted BIN in a frame on the exact origin of success_url.
// nextActionUrl: next_action.url from your server.
const pageOrigin = new URL(nextActionUrl).origin;
window.addEventListener('message', (event) => {
  if (event.origin !== pageOrigin || event.source !== frame.contentWindow) return;
  // A wake-up call only: ignore event.data. Ask your server to read the payment.
  fetch('/checkout/ORDER-2026-002/refresh', {method: 'POST', credentials: 'same-origin'});
});

Ödeme nesnesi

Oluşturma ve sorgulama çağrıları bu nesneyi data içinde döndürür; her ödeme webhook’u da bu nesneyi içerir. Nesnenin yapısı Hosted API ve Direct API için aynıdır.

AlanTürAçıklama
payment_idstring · LUVEI-ABCD-123456Ödemenin Payniko ID’si. Bu değeri siparişinizle birlikte saklayın.
merchant_order_idstringGönderdiğiniz şekliyle sipariş referansınız.
flowstringHosted API için hosted, Direct API için direct.
hosteddirect
statusstringÖdemenin güncel durumu; aşağıdaki durum tablosuna bakın.
requires_actionprocessingsucceededfailedexpiredcancelled
amountstringİki ondalık basamaklı tutar.
currencystringTutarın para birimi; para birimi belirtilmediyse sistemin ana para birimi.
environmentstringÖdemeyi oluşturan anahtarın ortamı.
sandboxlive
next_actionobject | nullMüşteriden beklenen bir sonraki işlem. Yalnızca status değeri requires_action iken doldurulur.
next_action.typestringHer zaman redirect.
next_action.urlstring · uriBu URL’yi müşterinin tarayıcısında, döndüğü şekliyle açın. URL’yi hiçbir zaman kendiniz oluşturmayın veya değiştirmeyin.
next_action.expires_atstring · date-timeURL’nin kullanılabileceği son zaman. Direct API’de bu değer 3-D Secure adımının bitişidir ve ödemenin kendi expires_at değerinden önce olabilir; Hosted API’de ise ödemenin son geçerlilik zamanıdır. Bu süre dolduğunda ödeme bir sonraki durumuna geçer ve next_action alanı null olur.
cardobject | nullMaskelenmiş kart bilgileri; belirlendiğinde doldurulur.
card.bin6string | nullİlk altı hane.
card.last_fourstring | nullSon dört hane.
card.networkstring | nullKart markası.
visamastercardamextroyunionpaydiscoverjcbdinersmaestro
card.bankstring | nullKartı çıkaran banka (biliniyorsa).
failureobject | nullBaşarısızlık nedeni. Yalnızca status değeri failed olduğunda doldurulur.
failure.codestringİş mantığınızda kullanılacak neden kodu; aşağıdaki listeye bakın.
failure.messagestringMüşteriye düz metin olarak gösterilmeye uygun kısa mesaj.
created_atstring · date-timeÖdemenin oluşturulduğu zaman.
expires_atstring · date-timeÖdemenin tamamlanması için son zaman.
completed_atstring · date-time | nullÖdemenin nihai bir duruma ulaştığı zaman.

next_action, requires_action durumunda bile kısa bir süre null olabilir; bu durumda ödemeyi yeniden sorgulayın. Kart numarasının tamamı, CVV, son kullanma tarihi ve kart sahibinin adı hiçbir zaman döndürülmez. card.network ve card.bank bilinmediğinde null olabilir.

Ödeme oluşturulurken gönderilen customer bilgileri bu nesnenin parçası değildir ve webhook'larda yer almaz.

Ödeme · Örnek

{
  "data": {
    "payment_id": "3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35",
    "merchant_order_id": "ORDER-2026-002",
    "flow": "direct",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/example-authentication-step",
      "expires_at": "2026-11-01T12:30:00.000Z"
    },
    "card": {
      "bin6": "435509",
      "last_four": "4242",
      "network": "visa",
      "bank": "Test Bank"
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-01T12:30:00.000Z",
    "completed_at": null
  }
}

Direct ödeme durumları

DurumAnlamÖnerilen işlem
requires_actionTarayıcının, 3-D Secure veya hosted BIN’e ait ödeme sayfası için next_action.url adresini açması gerekir.Müşteriyi next_action.url adresine yönlendirin.
processingBankanın yanıtı bekleniyor. Bu durum, oluşturma yanıtında doğrudan da dönebilir.Webhook'u bekleyin ve yeni bir ödeme oluşturmayın. Banka hiç yanıt vermezse ödeme mutabakata kadar processing durumunda kalabilir ve sonra failed ya da expired olarak biter.
succeededTutar tahsil edildi.Siparişi teslim edin.
failedÖdeme tamamlanmadı. Nedeni failure alanında yer alır.failure.message değerini gösterin. Yeniden denemek için yeni bir merchant_order_id ile yeni bir ödeme oluşturun.
expiredMüşteri ödemeyi expires_at zamanından önce tamamlamadı.Müşteri hâlâ ödeme yapmak istiyorsa yeni bir ödeme oluşturun.
cancelledÖdeme iptal edildi.Siparişi teslim etmeyin.
SenaryoDurumOlay
3-D Secure sonrasında onaylandırequires_action → succeededpayment.succeeded
Reddedildifailedpayment.failed
Banka yanıtı bekleniyorprocessingNihai sonuca kadar gönderilmez
Bankadan yanıt alınamadı; deneme zaman aşımına uğradıprocessing → failed / expiredpayment.failed
Müşteri ödemeye başlamadı ve süre doldu (müşteri ödemeyi yarıda bırakırsa ödeme failed durumuyla sonuçlanabilir)expiredpayment.failed

Burada listelenmeyen her durumu processing olarak değerlendirin ve ödemeyi yeniden sorgulayın.

Başarısızlık kodları

failed durumundaki bir ödeme failure.code ve failure.message alanlarını içerir. Kodu iş mantığınızda kullanın, mesajı ise müşteriye düz metin olarak gösterin.

KodAnlam
payment_declinedBanka ödemeyi reddetti.
do_not_honorBanka ödemeyi gerekçe belirtmeden reddetti.
insufficient_fundsKart bakiyesi yetersiz.
card_limit_reachedKart limitlerinden birine ulaşıldı.
card_details_invalidKart bilgileri geçersiz.
card_expiry_invalidSon kullanma tarihi geçersiz veya kartın süresi dolmuş.
card_security_code_invalidCVV geçersiz.
card_not_activeKart bloke edilmiş veya etkin değil.
card_brand_not_supportedBu kart markası desteklenmiyor.
three_ds_failed3-D Secure doğrulaması başarısız oldu veya tamamlanmadı.
payment_not_permittedKartın bu tür ödemelerde kullanılmasına izin verilmiyor.
amount_not_allowedBu kart için bu tutara izin verilmiyor.
currency_not_supportedBu para birimi bu kart için desteklenmiyor.
payment_details_rejectedBanka ödeme bilgilerini reddetti.
duplicate_paymentBanka ödemeyi mükerrer işlem olarak tespit etti.
payment_timed_outÖdeme öngörülen süre içinde tamamlanmadı.
bank_unavailableBankaya ulaşılamadı.
bank_requested_retryBanka, işlemin daha sonra yeniden denenmesini talep etti.
service_unavailableÖdeme şu anda işlenemedi.

Yeni kodlar eklenebilir; bilinmeyen kodları payment_declined olarak değerlendirin. failure.message bankanın kendi metnini içerebilir; aksi takdirde webhook’larda İngilizcedir, GET çağrılarında ise Accept-Language başlığına göre belirlenir.

Dönüş URL’si

Müşteri geri döndüğünde tarayıcı, yalnızca ödeme succeeded durumundaysa success_url adresine, diğer tüm durumlarda fail_url adresine yönlendirilir. URL’ye imzalı parametreler eklenir. Doğru sayfayı göstermek için bu parametreleri doğrulayın; siparişi ise yalnızca webhook veya bir GET çağrısı succeeded durumunu teyit ettikten sonra teslim edin.

Direct API: ödeyen 3-D Secure'dan döndüğünde sonuç henüz nihai değilse tarayıcı fail_url adresine yönlendirilmez; ödeyen tarafsız bir Payniko sonuç sayfasında kalır. Sonuç için webhook'a veya GET'e güvenin.

AlanAçıklama
payment_idÖdeme ID’si .
merchant_order_idSipariş referansınız. İmzalı değildir: yalnızca siparişi bulmak için kullanın; eşleştirmeyi payment_id üzerinden yapın.
statusYönlendirme anındaki ödeme durumu, GET ile aynı değerlerle. success_url üzerinde her zaman succeeded. fail_url üzerinde genellikle failed, expired veya cancelled; Hosted API'de ödeyen ödeme nihai duruma ulaşmadan ayrıldıysa (örneğin "Mağazaya dön" ile) requires_action veya processing olabilir.
failure_codeYalnızca status değeri failed olduğunda yer alır; failure.code ile aynıdır.
tsDönüş bağlantısının oluşturulduğu Unix zamanı. Müşteri bağlantıyı daha sonra açabileceğinden eski bir ts değeri tek başına sahtecilik göstergesi değildir.
key_versionBağlantıyı imzalayan anahtarı belirtir: ilgili ortamda oluşturulan ilk anahtar için 1, sonraki anahtar için 2 ve bu şekilde devam eder. Bir ödeme için bu değer hiçbir zaman değişmez.
sig64 karakterlik, küçük harfli onaltılık (hex) HMAC-SHA256 değeri.

İmzalanacak dize

ts + "." + payment_id + "." + status + "." + failure_code   // failure_code empty: keep the last dot
  1. key_version ile belirtilen anahtarın gizli anahtarını seçin. Anahtarlar sonradan değiştirilmiş olsa bile bu, ödemeyi oluşturan anahtardır.

  2. Dizeyi, URL kod çözümü (URL-decode) uygulanmış değerlerden oluşturun. failure_code yoksa sondaki noktayı koruyun.

  3. HMAC-SHA256 değerini hesaplayın ve sig ile sabit zamanlı olarak karşılaştırın. payment_id değerinin size ait ödemelerden birine karşılık geldiğini doğrulayın.

  4. Siparişi teslim etmeden önce ödemeyi GET /api/payments/{payment_id} ile sorgulayın (veya webhook’u bekleyin).

  • Kendi sorgu parametreleriniz korunur. URL’de yukarıdaki adlardan birine sahip bir parametre zaten varsa bu parametre değiştirilir.
  • Bağlantı geçici bir hata nedeniyle imzalanamadıysa URL bu parametrelerin hiçbirini içermez. Bu durumda sonucu bilinmiyor olarak kabul edin ve ödemeyi sorgulayın.
// Node.js 20+: check the signed parameters on success_url / fail_url.
import {createHmac, timingSafeEqual} from 'node:crypto';

// query: URLSearchParams of the return URL.
// secretFor(keyVersion): the API secret of that key, or undefined if you do not know it.
export function verifyReturn(query, secretFor) {
  const sig = query.get('sig') ?? '';
  const secret = secretFor(query.get('key_version'));
  if (!secret || !/^[0-9a-f]{64}$/.test(sig)) return false;
  const signed = [query.get('ts') ?? '', query.get('payment_id') ?? '',
    query.get('status') ?? '', query.get('failure_code') ?? ''].join('.');
  const expected = createHmac('sha256', secret).update(signed, 'utf8').digest();
  return timingSafeEqual(expected, Buffer.from(sig, 'hex'));
}
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
<?php
// PHP 8.1+: check the signed parameters on success_url / fail_url.
// $secretFor(string $keyVersion): the API secret of that key, or null if you do not know it.
function payniko_verify_return(array $query, callable $secretFor): bool
{
    $sig = (string) ($query['sig'] ?? '');
    $secret = $secretFor((string) ($query['key_version'] ?? ''));
    if (!$secret || !preg_match('/^[0-9a-f]{64}$/', $sig)) return false;
    $signed = implode('.', [(string) ($query['ts'] ?? ''), (string) ($query['payment_id'] ?? ''),
        (string) ($query['status'] ?? ''), (string) ($query['failure_code'] ?? '')]);
    return hash_equals(hash_hmac('sha256', $signed, $secret), $sig);
}

// $ok = payniko_verify_return($_GET, fn (string $v) => $secretsByKeyVersion[$v] ?? null);
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
# Python 3.9+: check the signed parameters on success_url / fail_url.
import hashlib, hmac, re


def verify_return(query, secret_for) -> bool:
    # query: dict-like (for example Flask request.args); secret_for(key_version) -> secret or None
    sig = query.get("sig", "")
    secret = secret_for(query.get("key_version"))
    if not secret or not re.fullmatch(r"[0-9a-f]{64}", sig):
        return False
    signed = ".".join([query.get("ts", ""), query.get("payment_id", ""),
                       query.get("status", ""), query.get("failure_code", "")])
    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

# Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.

Dönüş URL’leri · Örnek

https://merchant.example/payment/success?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=succeeded&ts=1793001234&key_version=1&sig=200e8a5d4a7a03ead362a2c076408f9d8764f8f636f58bd4c17d78a50d59eb8a
string to sign: 1793001234.LUVEI-PAYN-654321.succeeded.

https://merchant.example/payment/failure?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=failed&failure_code=insufficient_funds&ts=1793001234&key_version=1&sig=1cd0a104ec4ec4f5b2933d48d14c0eb33d949dd7f27936a067ab8f68b413b70d
string to sign: 1793001234.LUVEI-PAYN-654321.failed.insufficient_funds

secret used in this example (fake): example_secret_not_real
GET/api/payments/{payment_id}

Ödeme sorgulama

Güncel durumu içeren { "data": Payment } nesnesini döndürür. Sorgulama ödemede hiçbir değişiklik yapmadığından bu uç noktayı gerektiği sıklıkta çağırabilirsiniz: müşteri geri döndükten sonra, bir webhook tutarsız göründüğünde veya zaman aşımından sonra.

Parametreler

AlanTürZorunluAçıklama
payment_idstring · pathZorunluÖdeme oluşturulurken döndürülen payment_id. Aynı ortama ait her anahtarla bu ödeme sorgulanabilir.
./payniko.sh GET '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35'
import {api} from './payniko.mjs';

const payment = await api('GET', '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35');
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$payment = payniko_api('GET', '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35');
print_r($payment);
from payniko import api

payment = api("GET", "/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35")
print(payment)

Olası hatalar

Ödeme webhook’ları

Aşağıdaki olaylar ödemenin webhook_url adresine gönderilir. Her ödeme olayı, bir ödeme için en fazla bir kez gönderilir.

OlayAnlam
payment.succeededÖdemenin durumu succeeded. Siparişi teslim edin.
payment.failedÖdeme başarısız sonuçlandı: failed, expired veya cancelled. Bkz. data.status.
payment.updatedMüşterinin işlem yapması hâlâ gerekiyor (requires_action); örneğin ara bir ret sonrasında. Bu nihai bir sonuç değildir.
refund.* · payment.voided · dispute.*Bu ödemeye ilişkin sonraki olaylar (iadeler, iptaller, itirazlar). Bkz. Webhook’lar.

payment.succeeded

{
  "event_id": "e0743f4c-a297-4bf0-8085-ceb7d9d5e181",
  "event_type": "payment.succeeded",
  "environment": "sandbox",
  "occurred_at": "2026-11-01T12:02:00.000Z",
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "succeeded",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": null,
    "card": {
      "bin6": "400000",
      "last_four": "0002",
      "network": "visa",
      "bank": null
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": "2026-11-01T12:02:00.000Z"
  }
}

Her webhook’u doğrulayın: ödemeyi oluşturan anahtarın gizli anahtarıyla hesaplanan X-Timestamp + "." + raw body dizesinin HMAC-SHA256 değeri X-Signature ile aynı olmalı ve X-Timestamp 300 saniye içinde olmalıdır. Her event_id değerini bir kez kaydedin ve vakit kaybetmeden 2xx koduyla yanıt verin. Kod örnekleri ve yeniden deneme davranışı için bkz. Webhook’lar.

Hatalar

KodHTTPAnlamÖnerilen işlem
1000001422İstek geçersiz: bilinmeyen bir alan, geçersiz bir biçim, POST isteğinde sorgu dizesi, desteklenmeyen bir sorgu parametresi veya JSON nesnesi olmayan bir istek gövdesi. İlgili alanlar errors içinde listelenir.Listelenen alanları düzeltin.
1000009422Idempotency-Key eksik veya geçersiz.Her POST isteğinde A-Z a-z 0-9 _ - karakterlerinden oluşan, 8–128 karakter uzunluğunda bir anahtar gönderin.
0055502401 / 403Erişim reddedildi. 401: X-Api-Key veya X-Merchant-Code eksik, bir başlık yinelenmiş veya hatalı biçimlendirilmiş, anahtar tanınmıyor veya iptal edilmiş, üye işyeri kodu eşleşmiyor ya da X-Environment gönderilmiş ancak anahtarla uyuşmuyor. 403: hesap veya Direct API erişimi etkin değil.Başlıkları ve API anahtarını doğrulayın. İsteği otomatik olarak yeniden denemeyin.
0055503409Direct API çakışması: bu merchant_order_id için zaten bir ödeme mevcut, Idempotency-Key farklı bir istek gövdesiyle ya da bir Hosted ödeme için kullanılmış veya ilk istek hâlâ işleniyor.Ödemeyi GET ile sorgulayın. Yalnızca birebir aynı isteği yeniden gönderin.
0055210422BIN altı haneli değil.Kart numarasının ilk altı hanesini gönderin.
0055234409Kart bilgileri geçersiz: kart numarası, son kullanma tarihi veya CVV. metadata.developer_detail hangi alanın geçersiz olduğunu belirtir: card_number_invalid, card_expiry_invalid veya card_cvv_invalid.Müşteriden kart bilgilerini yeniden girmesini isteyin.
0055233409BIN tanınmıyor.Müşteriden farklı bir kart isteyin. Bu bir banka reddi değildir.
0055230409Bu kart bu ödeme için kullanılamaz.Müşteriden farklı bir kart isteyin. Bu bir banka reddi değildir.
0055231409Bu kart bu ödemede kabul edilmiyor.Müşteriden farklı bir kart isteyin.
0055221409Bu kartın limitlerinden birine ulaşıldı.Farklı bir kart kullanın veya daha sonra yeniden deneyin.
0055235422Tutar kabul edilmiyor (Direct API ve BIN sorgusu). Bilgi mevcut olduğunda amount_limit alanı min ve max değerlerini içerir.Tutarı güncelleyin.
0055001409Hesabınız bu ödeme için hazır değil. Sandbox anahtarıyla bu aynı zamanda sandbox simülasyonunun geçici olarak kullanılamadığı anlamına gelir (developer_detail: sandbox_unavailable), 9000013 ile eşdeğerdir.Destek ekibiyle iletişime geçin.
1000002404Bulunamadı: yol bilinmiyor ya da bu API ürünü platformda etkin değil. Hesabınız için kapatılmış bir API bunun yerine 403 döndürür (Hosted API'de 9000040, Direct API'de 0055502).Yolu doğrulayın; API'nin kullanılabilir olması gerekiyorsa destekle iletişime geçin.
1000011503Hizmet geçici olarak kullanılamıyor.Aynı isteği yeniden deneyin. Yeni kaynak oluşturmadan önce mevcut kaynakları sorgulayın.

Direct API hataları ayrıca metadata.retry_class alanını içerir (no_retry, retry_same_request, retry_after, read_order). Tarayıcı penceresinin kapanması veya sizin tarafınızda oluşan bir zaman aşımı, ödemenin başarısız olduğu anlamına gelmez; ödemeyi sorgulayın. Kodların tam listesi için bkz. Hata kodları.

Sandbox kartlarıyla test

Sandbox anahtarı kullanıldığında BIN sorgusu ve ödeme sabit test verileriyle çalışır: 435509 ve 400000 birer direct BIN, 540061 ise bir hosted BIN’dir. İleri tarihli herhangi bir son kullanma tarihi ve 3 haneli herhangi bir CVV kullanın. Ayrıntılar için bkz. Sandbox ve test kartları.

Kart numarasıSonuç
40000000000000103-D Secure olmadan onaylanır. Direct API’de oluşturma yanıtında durum doğrudan succeeded olarak döner.
4000000000000002 · 4355090000044242Test 3-D Secure adımının ardından succeeded durumu.
4000000000000051 · 43550900000400003-D Secure öncesinde reddedilir (do_not_honor). Hosted API: ödeme yaklaşık 5 dakika requires_action durumunda kalabilir ve failed olmadan önce payment.updated gönderebilir. Direct API: oluşturma yanıtı doğrudan failed döner.
4000000000000093 · 4355090000005151Test 3-D Secure adımının ardından insufficient_funds nedeniyle failed durumu.
4000000000000044Bankadan yanıt alınmaz ve 3-D Secure uygulanmaz; ödeme processing durumunda kalır.
4355090000089999Hosted API: 3-D Secure adımı olmadan processing. Direct API: test 3-D Secure adımı, ardından processing.

Direct API'de reddedilen bir test kartı oluşturma yanıtında doğrudan failed döner; ara requires_action yalnızca hosted BIN'in ödeme sayfasında görülür. Kararları webhook'a veya GET'e dayandırın.

Uçtan uca örnek

BIN sorgusu, ödeme oluşturma, 3-D Secure yönlendirmesi, dönüş URL’si ve webhook adımlarını kapsayan asgari bir Node.js sunucusu. Örnek, istek yardımcı fonksiyonunu ve Webhook’lar ile Dönüş URL’si bölümlerindeki doğrulama fonksiyonlarını kullanır.

server.mjs

// server.mjs: Direct API from start to end (Node.js 20+, no dependencies).
import http from 'node:http';
import {randomUUID} from 'node:crypto';
import {api} from './payniko.mjs';                  // request helper (Authentication)
import {verifyWebhook} from './verify-webhook.mjs'; // webhook check (Webhooks)
import {verifyReturn} from './verify-return.mjs';   // return URL check (Return URL)

const site = 'https://merchant.example';
const orders = new Map();      // use your database
const seenEvents = new Set();  // use your database
const readBody = async (req) => { const parts = []; for await (const p of req) parts.push(p); return Buffer.concat(parts); };

const reply = (res, data) => {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(data.status === 'requires_action' && data.next_action
    ? {redirect: data.next_action.url} : {status: data.status, failure: data.failure}));
};

http.createServer(async (req, res) => {
  const url = new URL(req.url, site);
  // 1. BIN check, 2. create the payment. Your own HTTPS form posts the card here.
  if (req.method === 'POST' && url.pathname === '/pay') {
    const form = JSON.parse((await readBody(req)).toString('utf8'));
    const bin6 = form.number.slice(0, 6);
    let mode = null;
    try {
      mode = (await api('GET', `/api/payments/bin/${bin6}?amount=100.00&currency=USD`)).data.payment_mode;
    } catch (error) {
      if (error.status === undefined || error.status >= 500 || error.status === 429) throw error;
    }
    if (mode !== 'direct' && mode !== 'hosted') { res.writeHead(422).end('Please use another card.'); return; }
    const orderId = 'ORDER-' + Date.now();
    const key = randomUUID();
    orders.set(orderId, {key, status: 'new'});
    const body = {
      merchant_order_id: orderId, amount: '100.00', currency: 'USD',
      customer: {email: form.email, phone: form.phone},
      webhook_url: `${site}/webhooks/payniko`,
      success_url: `${site}/payment/success`, fail_url: `${site}/payment/failure`,
    };
    if (mode === 'direct') {
      body.card = {number: form.number, holder_name: form.holder_name,
        expiry_month: form.expiry_month, expiry_year: form.expiry_year, cvv: form.cvv};
    } else {
      body.bin6 = bin6;                               // payer enters the card on the payment page
    }
    const {data} = await api('POST', '/api/payments/direct', body, key);
    Object.assign(orders.get(orderId), {paymentId: data.payment_id, status: data.status});
    reply(res, data);                                 // 3. browser goes to next_action.url
    return;
  }
  // Return URL: show a page, but trust only the webhook or a GET.
  if (req.method === 'GET' && (url.pathname === '/payment/success' || url.pathname === '/payment/failure')) {
    const signed = verifyReturn(url.searchParams, () => process.env.API_SECRET);
    let status = 'unknown';
    if (signed) status = (await api('GET', `/api/payments/${url.searchParams.get('payment_id')}`)).data.status;
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    res.end(status === 'succeeded' ? 'Thank you, payment received.' : 'Payment not completed.');
    return;
  }
  // Webhook: verify, store event_id once, answer 2xx fast.
  if (req.method === 'POST' && url.pathname === '/webhooks/payniko') {
    const raw = await readBody(req);
    if (!verifyWebhook(raw, req.headers, process.env.API_SECRET)) { res.writeHead(401).end(); return; }
    const event = JSON.parse(raw.toString('utf8'));
    if (!seenEvents.has(event.event_id)) {          // use a unique index in your database
      seenEvents.add(event.event_id);
      const order = orders.get(event.data.merchant_order_id);
      if (order && event.event_type.startsWith('payment.')) order.status = event.data.status;
    }
    res.writeHead(204).end();
    return;
  }
  res.writeHead(404).end();
}).listen(8080);