Zum Inhalt springen
Dokumentation

Direct API

Bei der Direct API ist das Kartenformular Teil Ihrer Website, und Ihr Server übermittelt die Kartendaten an Payniko. Der Browser des Zahlers wird nur für 3-D Secure und für hosted-BINs zu Payniko weitergeleitet. Verwenden Sie die Direct API, wenn sich das Kartenformular auf Ihrer eigenen Seite befinden muss.

Integrationsschritte

  1. BIN prüfen

    Sobald der Zahler sechs Ziffern eingegeben hat, rufen Sie GET /api/payments/bin/{bin6} auf. payment_mode gibt an, ob Sie die vollständigen Kartendaten in Ihrem Formular erfassen oder den Zahler auf die Zahlungsseite weiterleiten.

  2. Zahlung erstellen

    Rufen Sie POST /api/payments/direct mit den Kartendaten auf, bei einer hosted-BIN nur mit bin6.

  3. 3-D Secure abschließen

    Lautet der Status requires_action, leiten Sie den Browser auf next_action.url weiter. Anschließend kehrt der Browser mit signierten Parametern zu Ihrer success_url oder fail_url zurück.

  4. Ergebnis bestätigen

    Warten Sie auf den Webhook payment.succeeded oder payment.failed, oder rufen Sie GET /api/payments/{payment_id} auf. Führen Sie die Bestellung nur bei succeeded aus.

Zahlungsablauf

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

Voraussetzungen

  • Die Direct API wird pro Konto freigeschaltet. Ohne diese Freischaltung werden Anfragen mit 403 0055502 abgelehnt.
  • Schlüssel, Header und Signierung sind identisch mit der Hosted API; siehe Authentifizierung. Die Beispiele verwenden die Hilfsfunktionen für Anfragen.
GET/api/payments/bin/{bin6}

1. BIN prüfen

Vor dem Erstellen der Zahlung gibt die BIN-Prüfung an, ob die Karte in Ihrem Formular bezahlt werden kann (direct) oder ob die Zahlung auf der Zahlungsseite von Payniko abgeschlossen werden muss (hosted). Durch die Prüfung entsteht keine Belastung.

Parameter

FeldTypErforderlichBeschreibungBeispiel
bin6string · pathErforderlichErste sechs Ziffern der Karte.540061
amountstring · queryErforderlichBetrag mit zwei Nachkommastellen, zum Beispiel 100.00. Wird mit Ihren Kontolimits abgeglichen.100.00
currencystring · queryOptionalUSD, EUR, AED oder TRY. Standardmäßig die Hauptwährung des Systems. Verwenden Sie beim Erstellen der Zahlung dieselbe Währung.USD

Andere Abfrageparameter werden abgelehnt. Die Abfrageparameter sind nicht Bestandteil der Signatur.

./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)

Antwort · Beispiel

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

Antwort · 200

Liefert { "data": ... } mit den folgenden Feldern:

FeldTypBeschreibung
bin6stringDie übermittelten sechs Ziffern.
bankstring | nullKartenausgebende Bank, sofern bekannt.
networkstring | nullKartennetzwerk, sofern bekannt.
visamastercardamextroy
card_typestring | nullKartentyp, sofern bekannt.
creditdebitprepaid
card_programstring | nullKartenprogramm. Derzeit immer null.
countrystring | nullAusgabeland (ISO 3166-1 alpha-2), sofern bekannt.
domesticbooleantrue bei einer inländischen Karte; andernfalls oder wenn unbekannt false.
payment_modestring | nulldirect: Erfassen Sie die vollständigen Kartendaten. hosted: Leiten Sie den Zahler auf die Zahlungsseite weiter. Jeder andere Wert, einschließlich null: Erstellen Sie keine Zahlung.
directhosted

Umgang mit der Antwort

  • direct: Erfassen Sie die vollständigen Kartendaten und erstellen Sie die Zahlung mit card.
  • hosted: Erstellen Sie die Zahlung nur mit bin6. Der Zahler gibt die Kartendaten auf der Zahlungsseite von Payniko ein.
  • Jeder andere payment_mode oder ein Kartenfehler mit 409 (0055230, 0055231, 0055233): Erstellen Sie keine Zahlung und bitten Sie den Zahler mit einer neutralen Meldung um eine andere Karte, da es sich nicht um eine Ablehnung durch die Bank handelt. 422 0055235 weist auf ein Problem mit dem Betrag hin. Bei 429 oder 5xx wiederholen Sie dieselbe Prüfung später.

Beispiel

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.
}

Mögliche Fehler

POST/api/payments/directIdempotency-Key erforderlich

2. Zahlung erstellen

Liefert immer 200 mit der aktuellen Zahlung, auch wenn dieselbe Anfrage erneut gesendet wird. Eine Kartenablehnung ist kein HTTP-Fehler; die Antwort lautet 200 mit status: failed.

Anfrage-Body

FeldTypErforderlichBeschreibungBeispiel
merchant_order_idstringErforderlichIhre Bestellreferenz: 1–64 Zeichen aus A-Z a-z 0-9 . _ -. Eine Zahlung pro Referenz und Umgebung; bei der Eindeutigkeitsprüfung wird nicht zwischen Groß- und Kleinschreibung unterschieden.ORDER-2026-002
amountstringErforderlichBetrag mit zwei Nachkommastellen, zum Beispiel "100.00", innerhalb Ihrer Kontolimits."100.00"
currencystringOptionalStandardmäßig die Hauptwährung des Systems. Verwenden Sie dieselbe Währung wie bei der BIN-Prüfung.
USDEURAEDTRY
USD
webhook_urlstring · uriErforderlichÖffentliche HTTPS-URL auf Port 443, die die Webhooks für diese Zahlung empfängt, höchstens 500 Zeichen. Lokale Adressen werden nicht akzeptiert, auch nicht in der Sandbox; der Hostname muss zum Zeitpunkt der Anfrage auf eine öffentliche IP-Adresse auflösen, andernfalls 422 9000008.https://merchant.example/webhooks/payments
success_urlstring · uriErforderlichWeiterleitungsziel für den Browser, wenn die Zahlung erfolgreich war. HTTPS, höchstens 2048 Zeichen, ohne Benutzerinformationen und Fragment. In der Sandbox werden zusätzlich http://localhost, 127.0.0.1 und [::1] akzeptiert.https://merchant.example/payment/success
fail_urlstring · uriErforderlichWeiterleitungsziel für den Browser in allen anderen Fällen. Es gelten dieselben Regeln wie für success_url.https://merchant.example/payment/failure
customerobjectErforderlichKontaktdaten des Zahlenden; sie werden für das Zahlungsergebnis, den Beleg und die Prüfschritte der Bank verwendet. Nur email und phone werden akzeptiert; jedes andere Feld innerhalb von customer wird mit 422 1000001 abgelehnt.{ … }
customer.emailstringErforderlichDie echte E-Mail-Adresse des Zahlenden: RFC 5322, höchstens 254 Zeichen, Domain mit Punkt. Sie wird in Kleinbuchstaben gespeichert.[email protected]
customer.phonestringErforderlichDie echte Telefonnummer des Zahlenden im internationalen Format (+ Ländervorwahl, E.164), zum Beispiel +37061234567. Regel für die Türkei: +90 gefolgt von der 10-stelligen nationalen Nummer (5xx mobil, 2xx, 3xx, 4xx Festnetz, 8xx Sonderrufnummern). Eine Nummer ohne Ländervorwahl wird als türkische Nummer (+90) behandelt.+37061234567
cardobjectBedingt erforderlichDie vom Zahler eingegebenen Kartendaten (siehe Tabelle unten). Senden Sie entweder card oder bin6, nicht beides.{ … }
bin6stringBedingt erforderlichNur bei einer hosted-BIN: die ersten sechs Ziffern. Der Zahler gibt die Kartendaten auf der Zahlungsseite ein. Eine Anfrage für eine direct-BIN, die nur bin6 enthält, liefert 409 0055230.540061
card.numberstringErforderlich13–19 Ziffern ohne Leerzeichen, mit gültiger Prüfziffer (15 Ziffern bei American Express).4355090000044242
card.holder_namestringErforderlichName auf der Karte, 2–120 Zeichen.JONAS KAZLAUSKAS
card.expiry_monthstringErforderlichZwei Ziffern, 01 bis 12.12
card.expiry_yearstringErforderlichZwei Ziffern, zum Beispiel 30. Die Karte darf nicht abgelaufen sein, und das Jahr darf höchstens 30 Jahre in der Zukunft liegen.30
card.cvvstringErforderlichGenau 3 Ziffern, bei American Express 4 Ziffern. Niemals speichern oder protokollieren.123

Alle Kartenwerte sind Zeichenketten. Unbekannte Felder innerhalb von card werden abgelehnt.

Bei einer hosted-BIN fragt die Zahlungsseite den Zahlenden nicht erneut nach E-Mail-Adresse und Telefonnummer. Aus Datenschutzgründen wird customer weder im Zahlungsobjekt noch in Webhooks zurückgegeben.

FeldGrundMeldung in errors
customer.emailUngültiges Format oder länger als 254 Zeichen.customer.email must be a valid e-mail address (max 254 characters).
customer.emailPlatzhalter-, Beispiel- oder Wegwerfadresse.customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted.
customer.phoneKeine gültige Nummer im internationalen Format.customer.phone must be a valid phone number in international format (e.g. +37061234567).
customer.phoneWiederholtes oder aufeinanderfolgendes Ziffernmuster.customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted.
customer.phoneTürkische Nummer nicht in der Form +90 gefolgt von 10 Ziffern, die mit 2, 3, 4, 5 oder 8 beginnen.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."
    ]
  }
}

Wird eine Anfrage wegen customer abgelehnt, zeigen Sie dem Zahlenden den Grund klar an (zum Beispiel: Geben Sie Ihre eigene E-Mail-Adresse oder Telefonnummer ein) und lassen Sie ihn das Feld korrigieren, bevor Sie die Zahlung erneut erstellen. Die Direct API gibt die Meldungen in errors übersetzt zurück; die Tabelle zeigt den englischen Text.

Payniko-Zahlungslink-Seiten wenden dieselben Regeln auf die dort vom Zahlenden eingegebene E-Mail-Adresse und Telefonnummer an und zeigen dem Zahlenden den Grund einer Ablehnung.

Mit Kartendaten · Beispiel

{
  "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)

Antwort · Beispiel

{
  "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
  }
}

Mit bin6 für eine hosted-BIN · Beispiel

{
  "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"
}

Auswertung der Antwort

StatusEmpfohlene Maßnahme
requires_actionLeiten Sie den Browser auf next_action.url weiter (3-D Secure oder, bei einer hosted-BIN, die Zahlungsseite).
processingWarten Sie auf den Webhook und erstellen Sie keine neue Zahlung.
succeededFühren Sie die Bestellung aus.
failedZeigen Sie failure.message an und bitten Sie den Zahler um eine andere Karte.
  • Der Aufruf kann einige Sekunden dauern, während Payniko mit der Bank kommuniziert. Verwenden Sie ein Timeout von mindestens 60 Sekunden und senden Sie die Anfrage nach einem Timeout mit demselben Idempotency-Key und Body erneut. Solange die erste Anfrage noch läuft (bis zu 3 Minuten), liefert die Wiederholung 409 0055503 mit metadata.retry_class: retry_same_request: Warten Sie einige Sekunden und senden Sie dieselbe Anfrage erneut.
  • Bei einer hosted-BIN wird eine mitgesendete card ignoriert, und der Zahler setzt den Vorgang auf der Zahlungsseite fort.
  • Existiert für die merchant_order_id bereits eine Zahlung oder wird derselbe Idempotency-Key mit einem anderen Body gesendet, wird die Anfrage mit 409 0055503 abgelehnt.
  • Ungültige Kartendaten werden mit 409 0055234 abgelehnt. metadata.developer_detail gibt das betroffene Feld an: card_number_invalid, card_expiry_invalid oder card_cvv_invalid. Bitten Sie den Zahler, die Kartendaten erneut einzugeben.
  • Fehlende oder ungültige Kontaktdaten werden mit 422 1000001 abgelehnt; errors nennt customer, customer.email oder customer.phone.

3. 3-D Secure und Browser

Lautet der Status requires_action, muss der Browser des Zahlers next_action.url öffnen; dort führt die Bank 3-D Secure durch, oder der Zahler setzt den Vorgang auf der Zahlungsseite fort.

  • Leiten Sie die gesamte Seite auf next_action.url weiter oder öffnen Sie die URL in einem neuen Tab; beide Methoden werden immer unterstützt.
  • Die Seite fragt die Kartendaten nicht erneut ab; sie erfasst nur die Browserdaten, die die Bank benötigt.
  • Verwenden Sie immer die neueste next_action.url vor deren next_action.expires_at (dem Ende des 3-D-Secure-Schritts). Öffnen Sie niemals eine frühere URL erneut.
  • Anschließend wird der Browser mit signierten Parametern zu success_url (nur bei succeeded) oder zu fail_url weitergeleitet (siehe Rücksprung-URL). Rufen Sie danach die Zahlung ab.

Einbettung in Frames

Die 3-D-Secure-Seite kann nicht in einem Frame angezeigt werden; leiten Sie die gesamte Seite weiter oder öffnen Sie einen neuen Tab. Nur die Zahlungsseite einer hosted-BIN darf in einen Frame eingebettet werden, ausschließlich mit Live-Schlüsseln und nur auf genau dem Origin der success_url der Zahlung. Die Zahlungsseite der Hosted API kann niemals in einen Frame eingebettet werden.

Die Zahlungsseite einer hosted-BIN bettet die eigene sichere Zahlungs- und 3-D-Secure-Oberfläche des Zahlungsdienstleisters ein und kann als iFrame in Ihrem eigenen Zahlungsformular angezeigt werden.

Wenn Sie die Zahlungsseite einer hosted-BIN in einen Frame einbetten, dienen die Nachrichten, die sie an Ihr Fenster sendet, nur als Signal, die Zahlung zu prüfen. Akzeptieren Sie diese Nachrichten nur vom Origin von next_action.url und von Ihrem Frame, ignorieren Sie ihren Inhalt und lassen Sie Ihren Server die Zahlung abrufen.

Browser

// 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'});
});

Zahlungsobjekt

Aufrufe zum Erstellen und Abrufen liefern dieses Objekt in data, und jeder Zahlungs-Webhook enthält es. Seine Struktur ist bei der Hosted API und der Direct API identisch.

FeldTypBeschreibung
payment_idstring · LUVEI-ABCD-123456Payniko-ID der Zahlung . Speichern Sie sie zusammen mit Ihrer Bestellung.
merchant_order_idstringIhre Bestellreferenz, genau wie übermittelt.
flowstringhosted für die Hosted API, direct für die Direct API.
hosteddirect
statusstringAktueller Status der Zahlung; siehe Statustabelle unten.
requires_actionprocessingsucceededfailedexpiredcancelled
amountstringBetrag mit zwei Nachkommastellen.
currencystringWährung des Betrags; die Hauptwährung des Systems, wenn keine angegeben wurde.
environmentstringUmgebung des Schlüssels, mit dem die Zahlung erstellt wurde.
sandboxlive
next_actionobject | nullDie nächste vom Zahler erforderliche Aktion. Nur gesetzt, solange status den Wert requires_action hat.
next_action.typestringImmer redirect.
next_action.urlstring · uriÖffnen Sie diese URL im Browser des Zahlers genau so, wie sie zurückgegeben wird. Erstellen oder verändern Sie sie niemals selbst.
next_action.expires_atstring · date-timeFrist für die Verwendung der URL. Bei der Direct API ist dies das Ende des 3-D-Secure-Schritts und kann vor dem eigenen expires_at der Zahlung liegen; bei der Hosted API ist es die Frist der Zahlung. Nach Ablauf geht die Zahlung in ihren nächsten Status über, und next_action wird null.
cardobject | nullMaskierte Kartendaten, sobald bekannt.
card.bin6string | nullErste sechs Ziffern.
card.last_fourstring | nullLetzte vier Ziffern.
card.networkstring | nullKartennetzwerk.
visamastercardamextroyunionpaydiscoverjcbdinersmaestro
card.bankstring | nullKartenausgebende Bank, sofern bekannt.
failureobject | nullGrund für das Fehlschlagen. Nur gesetzt, wenn status den Wert failed hat.
failure.codestringCode des Fehlergrunds zur Verwendung in Ihrer Programmlogik; siehe Liste unten.
failure.messagestringKurze Meldung, die sich zur Anzeige für den Zahler als reiner Text eignet.
created_atstring · date-timeZeitpunkt, zu dem die Zahlung erstellt wurde.
expires_atstring · date-timeFrist für den Abschluss der Zahlung.
completed_atstring · date-time | nullZeitpunkt, zu dem die Zahlung einen Endstatus erreicht hat.

next_action kann selbst bei requires_action kurzzeitig null sein; rufen Sie die Zahlung in diesem Fall erneut ab. Die vollständige Kartennummer, der CVV, das Ablaufdatum und der Name des Karteninhabers werden nie zurückgegeben. card.network und card.bank können null sein, wenn sie unbekannt sind.

Die beim Erstellen der Zahlung gesendeten customer-Daten sind nicht Teil dieses Objekts und erscheinen nicht in Webhooks.

Zahlung · Beispiel

{
  "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
  }
}

Status von Direct-Zahlungen

StatusBedeutungEmpfohlene Maßnahme
requires_actionDer Browser muss next_action.url für 3-D Secure oder für die Zahlungsseite einer hosted-BIN öffnen.Leiten Sie den Zahler zu next_action.url weiter.
processingDie Antwort der Bank steht noch aus. Dieser Status kann auch direkt in der Antwort auf die Erstellungsanfrage zurückgegeben werden.Warten Sie auf den Webhook und erstellen Sie keine neue Zahlung. Antwortet die Bank nie, kann die Zahlung bis zum Abgleich in processing bleiben und endet dann als failed oder expired.
succeededDie Gelder wurden eingezogen.Führen Sie die Bestellung aus.
failedDie Zahlung wurde nicht abgeschlossen. failure enthält den Grund.Zeigen Sie failure.message an. Für einen erneuten Versuch erstellen Sie eine neue Zahlung mit einer neuen merchant_order_id.
expiredDer Zahler hat die Zahlung nicht vor expires_at abgeschlossen.Wenn der Zahler weiterhin zahlen möchte, erstellen Sie eine neue Zahlung.
cancelledDie Zahlung wurde abgebrochen.Führen Sie die Bestellung nicht aus.
SzenarioStatusEreignis
Nach 3-D Secure genehmigtrequires_action → succeededpayment.succeeded
Abgelehntfailedpayment.failed
Antwort der Bank ausstehendprocessingKeines bis zum endgültigen Ergebnis
Keine Antwort der Bank; Zeitüberschreitung beim Versuchprocessing → failed / expiredpayment.failed
Der Zahler hat nicht begonnen, und die Frist ist abgelaufen (bricht der Zahler die Zahlung vorzeitig ab, kann sie mit failed enden)expiredpayment.failed

Behandeln Sie jeden hier nicht aufgeführten Status wie processing und rufen Sie die Zahlung erneut ab.

Fehlergründe

Eine Zahlung mit dem Status failed enthält failure.code und failure.message. Verwenden Sie den Code in Ihrer Programmlogik und zeigen Sie die Meldung dem Zahler als reinen Text an.

CodeBedeutung
payment_declinedDie Bank hat die Zahlung abgelehnt.
do_not_honorDie Bank hat die Zahlung ohne Angabe von Gründen abgelehnt.
insufficient_fundsDie Karte weist keine ausreichende Deckung auf.
card_limit_reachedEin Kartenlimit wurde erreicht.
card_details_invalidDie Kartendaten sind ungültig.
card_expiry_invalidDas Ablaufdatum ist ungültig oder die Karte ist abgelaufen.
card_security_code_invalidDer CVV ist ungültig.
card_not_activeDie Karte ist gesperrt oder nicht aktiv.
card_brand_not_supportedDieses Kartennetzwerk wird nicht unterstützt.
three_ds_failedDie 3-D-Secure-Authentifizierung ist fehlgeschlagen oder wurde nicht abgeschlossen.
payment_not_permittedDie Karte ist für diese Zahlungsart nicht zugelassen.
amount_not_allowedDer Betrag ist für diese Karte nicht zulässig.
currency_not_supportedDie Währung wird für diese Karte nicht unterstützt.
payment_details_rejectedDie Bank hat die Zahlungsdaten abgelehnt.
duplicate_paymentDie Bank hat die Zahlung als Duplikat eingestuft.
payment_timed_outDie Zahlung wurde nicht rechtzeitig abgeschlossen.
bank_unavailableDie Bank war nicht erreichbar.
bank_requested_retryDie Bank hat um einen erneuten Versuch zu einem späteren Zeitpunkt gebeten.
service_unavailableDie Zahlung konnte derzeit nicht verarbeitet werden.

Es können neue Codes hinzukommen; behandeln Sie jeden unbekannten Code wie payment_declined. failure.message kann den Originaltext der Bank enthalten; andernfalls ist die Meldung in Webhooks auf Englisch und richtet sich bei GET nach Accept-Language.

Rücksprung-URL

Wenn der Zahler zurückkehrt, wird der Browser nur dann zu success_url weitergeleitet, wenn die Zahlung succeeded ist, in allen anderen Fällen zu fail_url. An die URL werden signierte Parameter angehängt. Prüfen Sie diese, um die richtige Seite anzuzeigen, und führen Sie die Bestellung erst aus, wenn der Webhook oder ein GET succeeded bestätigt.

Direct API: Ist das Ergebnis noch nicht endgültig, wenn der Zahlende von 3-D Secure zurückkehrt, wird der Browser nicht zu fail_url weitergeleitet; der Zahlende bleibt auf einer neutralen Payniko-Ergebnisseite. Verlassen Sie sich für das Ergebnis auf den Webhook oder ein GET.

FeldBeschreibung
payment_idDie Zahlungs-ID .
merchant_order_idIhre Bestellreferenz. Sie ist nicht signiert: Verwenden Sie sie nur, um die Bestellung zu finden, und gleichen Sie anhand von payment_id ab.
statusZahlungsstatus zum Zeitpunkt der Weiterleitung, mit denselben Werten wie bei GET. Auf success_url immer succeeded. Auf fail_url in der Regel failed, expired oder cancelled; bei der Hosted API kann er requires_action oder processing sein, wenn der Zahlende die Seite verlassen hat, bevor die Zahlung einen Endstatus erreicht hat, zum Beispiel über "Zurück zum Shop".
failure_codeNur vorhanden, wenn status den Wert failed hat; identisch mit failure.code.
tsUnix-Zeit, zu der der Rücksprung-Link erstellt wurde. Der Zahler kann den Link später öffnen; ein älterer ts allein ist daher kein Hinweis auf eine Fälschung.
key_versionGibt den Schlüssel an, mit dem der Link signiert wurde: 1 für den ersten in dieser Umgebung erstellten Schlüssel, 2 für den nächsten und so weiter. Für eine bestimmte Zahlung ändert sich der Wert nie.
sigHMAC-SHA256 als Hexadezimalwert in Kleinbuchstaben, 64 Zeichen.

Zu signierende Zeichenkette

ts + "." + payment_id + "." + status + "." + failure_code   // failure_code empty: keep the last dot
  1. Wählen Sie den geheimen Schlüssel des durch key_version angegebenen Schlüssels. Dies ist der Schlüssel, mit dem die Zahlung erstellt wurde, auch wenn Schlüssel seitdem ersetzt wurden.

  2. Bilden Sie die Zeichenkette aus den URL-decodierten Werten. Fehlt failure_code, bleibt der abschließende Punkt erhalten.

  3. Berechnen Sie den HMAC-SHA256 und vergleichen Sie ihn in konstanter Zeit mit sig. Stellen Sie sicher, dass payment_id zu einer Ihrer Zahlungen gehört.

  4. Rufen Sie die Zahlung mit GET /api/payments/{payment_id} ab (oder warten Sie auf den Webhook), bevor Sie die Bestellung ausführen.

  • Ihre eigenen Abfrageparameter bleiben erhalten. Enthält die URL bereits einen Parameter mit einem der oben genannten Namen, wird er ersetzt.
  • Konnte der Link aufgrund eines vorübergehenden Fehlers nicht signiert werden, enthält die URL keinen dieser Parameter. Behandeln Sie das Ergebnis als unbekannt und rufen Sie die Zahlung ab.
// 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.

Rücksprung-URLs · Beispiel

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}

Zahlung abrufen

Liefert { "data": Payment } mit dem aktuellen Status. Das Abrufen verändert die Zahlung nie; Sie können diesen Endpoint daher so oft wie erforderlich aufrufen: nach dem Rücksprung, wenn ein Webhook unstimmig erscheint, oder nach einer Zeitüberschreitung.

Parameter

FeldTypErforderlichBeschreibung
payment_idstring · pathErforderlichDie payment_id, die beim Erstellen der Zahlung zurückgegeben wurde. Jeder Schlüssel aus derselben Umgebung kann die Zahlung abrufen.
./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)

Mögliche Fehler

Zahlungs-Webhooks

Die folgenden Ereignisse werden an die webhook_url der Zahlung gesendet. Jedes Zahlungsereignis wird pro Zahlung höchstens einmal gesendet.

EreignisBedeutung
payment.succeededDer Status der Zahlung ist succeeded. Führen Sie die Bestellung aus.
payment.failedDie Zahlung wurde ohne Erfolg beendet: failed, expired oder cancelled. Siehe data.status.
payment.updatedEine Aktion des Zahlers ist noch erforderlich (requires_action), zum Beispiel nach einer vorläufigen Ablehnung. Dies ist kein endgültiges Ergebnis.
refund.* · payment.voided · dispute.*Nachfolgende Ereignisse zu dieser Zahlung (Rückerstattungen, Stornierungen, Anfechtungen). Siehe Webhooks.

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"
  }
}

Prüfen Sie jeden Webhook: Der HMAC-SHA256 von X-Timestamp + "." + raw body, berechnet mit dem geheimen Schlüssel des API-Schlüssels, mit dem die Zahlung erstellt wurde, muss X-Signature entsprechen, und X-Timestamp muss innerhalb von 300 Sekunden liegen. Speichern Sie jede event_id nur einmal und antworten Sie umgehend mit 2xx. Codebeispiele und das Wiederholungsverhalten finden Sie unter Webhooks.

Fehler

CodeHTTPBedeutungEmpfohlene Maßnahme
1000001422Die Anfrage ist ungültig: ein unbekanntes Feld, ein ungültiges Format, Abfrageparameter bei einem POST, ein nicht unterstützter Abfrageparameter oder ein Body, der kein JSON-Objekt ist. errors listet die betroffenen Felder auf.Korrigieren Sie die aufgeführten Felder.
1000009422Idempotency-Key fehlt oder ist ungültig.Senden Sie bei jedem POST einen Idempotenzschlüssel mit 8–128 Zeichen aus A-Z a-z 0-9 _ -.
0055502401 / 403Zugriff verweigert. 401: X-Api-Key oder X-Merchant-Code fehlt, ein Header ist doppelt vorhanden oder fehlerhaft, der Schlüssel ist unbekannt oder widerrufen, der Händlercode stimmt nicht überein, oder X-Environment wurde gesendet und passt nicht zum Schlüssel. 403: Das Konto oder der Zugang zur Direct API ist nicht aktiv.Prüfen Sie die Header und den API-Schlüssel. Wiederholen Sie die Anfrage nicht automatisch.
0055503409Konflikt in der Direct API: Für diese merchant_order_id existiert bereits eine Zahlung, der Idempotency-Key wurde mit einem anderen Body oder für eine Hosted-Zahlung verwendet, oder die ursprüngliche Anfrage wird noch verarbeitet.Rufen Sie die Zahlung per GET ab. Senden Sie nur eine identische Anfrage erneut.
0055210422Die BIN ist nicht sechsstellig.Senden Sie die ersten sechs Ziffern der Kartennummer.
0055234409Die Kartendaten sind ungültig: Kartennummer, Ablaufdatum oder CVV. metadata.developer_detail benennt das Feld: card_number_invalid, card_expiry_invalid oder card_cvv_invalid.Bitten Sie den Zahler, die Kartendaten erneut einzugeben.
0055233409Die BIN ist unbekannt.Bitten Sie den Zahler um eine andere Karte. Es handelt sich nicht um eine Ablehnung durch die Bank.
0055230409Diese Karte kann für diese Zahlung nicht verwendet werden.Bitten Sie den Zahler um eine andere Karte. Es handelt sich nicht um eine Ablehnung durch die Bank.
0055231409Diese Karte wird für diese Zahlung nicht akzeptiert.Bitten Sie den Zahler um eine andere Karte.
0055221409Ein Limit für diese Karte wurde erreicht.Verwenden Sie eine andere Karte oder wiederholen Sie den Vorgang zu einem späteren Zeitpunkt.
0055235422Der Betrag wird nicht akzeptiert (Direct API und BIN-Prüfung). amount_limit enthält min und max, sofern verfügbar.Passen Sie den Betrag an.
0055001409Ihr Konto ist für diese Zahlung nicht bereit. Mit einem Sandbox-Schlüssel bedeutet dies auch, dass die Sandbox-Simulation vorübergehend nicht verfügbar ist (developer_detail: sandbox_unavailable), gleichbedeutend mit 9000013.Wenden Sie sich an den Support.
1000002404Nicht gefunden: Der Pfad ist unbekannt, oder dieses API-Produkt ist auf der Plattform nicht aktiviert. Eine für Ihr Konto deaktivierte API liefert stattdessen 403 (9000040 bei der Hosted API, 0055502 bei der Direct API).Prüfen Sie den Pfad; wenden Sie sich an den Support, wenn die API verfügbar sein sollte.
1000011503Der Dienst ist vorübergehend nicht verfügbar.Wiederholen Sie dieselbe Anfrage. Rufen Sie bestehende Ressourcen ab, bevor Sie neue erstellen.

Fehler der Direct API enthalten zusätzlich metadata.retry_class (no_retry, retry_same_request, retry_after, read_order). Ein geschlossenes Browserfenster oder eine Zeitüberschreitung auf Ihrer Seite bedeutet nicht, dass die Zahlung fehlgeschlagen ist; rufen Sie die Zahlung ab. Die vollständige Liste finden Sie unter Fehlercodes.

Tests mit Sandbox-Karten

Mit einem Sandbox-Schlüssel verwenden BIN-Prüfung und Zahlung feste Testdaten: 435509 und 400000 sind direct-BINs, 540061 ist eine hosted-BIN. Verwenden Sie ein beliebiges Ablaufdatum in der Zukunft und einen beliebigen dreistelligen CVV. Einzelheiten finden Sie unter Sandbox und Testkarten.

KartennummerErgebnis
4000000000000010Ohne 3-D Secure genehmigt. Bei der Direct API liefert bereits die Antwort auf die Erstellungsanfrage succeeded.
4000000000000002 · 43550900000442423-D-Secure-Testschritt, anschließend succeeded.
4000000000000051 · 4355090000040000Vor 3-D Secure abgelehnt (do_not_honor). Hosted API: Die Zahlung kann etwa 5 Minuten in requires_action bleiben und payment.updated senden, bevor sie als failed endet. Direct API: Die Antwort auf die Erstellung liefert bereits failed.
4000000000000093 · 43550900000051513-D-Secure-Testschritt, anschließend failed mit insufficient_funds.
4000000000000044Keine Antwort der Bank und kein 3-D Secure; die Zahlung verbleibt im Status processing.
4355090000089999Hosted API: processing ohne 3-D-Secure-Schritt. Direct API: Test-3-D-Secure-Schritt, danach processing.

Bei der Direct API liefert eine abgelehnte Testkarte direkt in der Antwort auf die Erstellung failed; ein zwischenzeitliches requires_action tritt nur auf der Zahlungsseite einer hosted-BIN auf. Stützen Sie Entscheidungen auf den Webhook oder ein GET.

Vollständiges Beispiel

Ein minimaler Node.js-Server mit BIN-Prüfung, Erstellung der Zahlung, Weiterleitung zu 3-D Secure, Rücksprung-URL und Webhook. Er verwendet die Hilfsfunktion für Anfragen und die Prüffunktionen aus Webhooks und Rücksprung-URL.

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);