ΠΡΟΓΡΑΜΜΑΤΙΣΤΕΣ

Direct API (v4)

Συλλέξτε την κάρτα στη δική σας φόρμα πληρωμής και στείλτε την στη Manuell από τον διακομιστή σας. Ο πελάτης επιβεβαιώνει την πληρωμή με 3-D Secure και λαμβάνετε το αποτέλεσμα με webhook.

  • Βασικό URLhttps://www.manuellsolutions.com/api/v4
  • Έκδοση APIv4

Ξεκινήστε από την επισκόπηση

Περιβάλλον

Δοκιμάστε χωρίς κίνηση πραγματικών χρημάτων.

Πραγματικές πληρωμές. Απαιτείται live κλειδί.

Επισκόπηση

Ο διακομιστής σας μιλά απευθείας με τη Manuell: ρωτά ποια ροή χρειάζεται μια κάρτα, δημιουργεί την παραγγελία με τα στοιχεία της κάρτας και την παρακολουθεί μέχρι να πληρωθεί ή να αποτύχει.

Βασικό URL
https://www.manuellsolutions.com/api/v4
Νόμισμα
Μόνο TRY. Μία πληρωμή ανά παραγγελία, χωρίς δόσεις.
Ποσά
Συμβολοσειρές με 2 δεκαδικά, π.χ. "1500.00".
Περιβάλλον
sandbox ή live, στην κεφαλίδα X-CEK-Environment. Πρέπει να ταιριάζει με το περιβάλλον του κλειδιού σας.
Σφάλματα
application/problem+json (RFC 7807) με code 7 ψηφίων.
Όριο ρυθμού
300 αιτήματα ανά λεπτό ανά κλειδί (600 ανά λεπτό ανά διεύθυνση IP πριν την ταυτοποίηση). Πέρα από αυτό: 429 με Retry-After.
Διπλότυπα
Δεν υπάρχει Idempotency-Key. Το merchant_order_id προστατεύει από διπλές παραγγελίες.
  1. Αναζητήστε το BIN

    Στείλτε τα πρώτα 6 ψηφία της κάρτας και το ποσό στο GET /api/v4/bins/{bin}. Το payment_mode σας λέει αν θα συλλέξετε εσείς την κάρτα (direct) ή θα στείλετε τον πελάτη σε φιλοξενούμενη σελίδα (hosted).

  2. Δημιουργήστε την παραγγελία

    Στείλτε POST /api/v4/orders με την κάρτα (direct) ή μόνο με το BIN (hosted). Η παραγγελία επιστρέφει ως requires_action, requires_hosted, pending ή failed.

  3. Ολοκληρώστε και επιβεβαιώστε

    Οδηγήστε τον πελάτη στο redirect_url (3-D Secure) ή στο hosted_url. Το τελικό αποτέλεσμα φτάνει με webhook· αν καθυστερεί, διαβάστε την παραγγελία με GET /api/v4/orders/{merchantOrderId}.

Ρύθμιση στον πίνακα

  • Δημιουργήστε κλειδί API και μυστικό κλειδί για κάθε περιβάλλον στο “API Key & Ενσωμάτωση”. Φυλάξτε το μυστικό στον διακομιστή σας.
  • Προσθέστε τις δημόσιες διευθύνσεις IP των διακομιστών σας στη λίστα επιτρεπόμενων IP του περιβάλλοντος. Όσο η λίστα είναι κενή, κάθε κλήση απορρίπτεται με 403 και κωδικό 0055501.
  • Ορίστε μια διεύθυνση webhook HTTPS. Ο κεντρικός υπολογιστής της πρέπει να είναι δημόσιος.
  • Ορίστε τις διευθύνσεις επιστροφής για επιτυχία και αποτυχία (HTTPS στο live). Χωρίς αυτές ο πελάτης μένει στην οθόνη αποτελέσματος μετά την πληρωμή.
  • Ο κωδικός εμπόρου σας είναι MRC- ακολουθούμενο από τον 6ψήφιο αριθμό λογαριασμού σας, για παράδειγμα MRC-123456.

Έλεγχος ταυτότητας

Κάθε κλήση φέρει κεφαλίδες X-CEK-*. Η υπογραφή είναι ένα HMAC-SHA256 μιας συμβολοσειράς που ενώνει τη χρονοσήμανση, τη μέθοδο, τη διαδρομή και το hash του σώματος.

Υποχρεωτικές κεφαλίδες

ΚεφαλίδαΠεριγραφή
X-CEK-Api-KeyΤο κλειδί API του περιβάλλοντος.
X-CEK-Environmentsandbox ή live. Πρέπει να ταιριάζει με το περιβάλλον του κλειδιού.
X-CEK-Merchant-CodeΟ κωδικός εμπόρου σας, π.χ. MRC-123456. Υποχρεωτικός στο sandbox· στο live, αν τον στείλετε, πρέπει να ταιριάζει με τον λογαριασμό σας.
X-CEK-TimestampΤρέχων χρόνος Unix σε δευτερόλεπτα (10 ψηφία). Απόκλιση πάνω από 300 δευτερόλεπτα απορρίπτεται.
X-CEK-Signature64 πεζοί χαρακτήρες hex, υπολογισμένοι όπως περιγράφεται παρακάτω.
Content-Typeapplication/json στα αιτήματα POST.

Πώς υπογράφεται ένα αίτημα

  1. Πάρτε το σώμα ακριβώς όπως το στέλνετε και υπολογίστε το sha256(body) ως hex με πεζά. Για αίτημα χωρίς σώμα χρησιμοποιήστε το hash της κενής συμβολοσειράς.

  2. Ενώστε τη χρονοσήμανση, τη μέθοδο με κεφαλαία, τη διαδρομή και το hash του σώματος με αλλαγές γραμμής (\n). Η διαδρομή είναι η πλήρης διαδρομή χωρίς κεντρικό υπολογιστή και συμβολοσειρά ερωτήματος, π.χ. /api/v4/orders.

  3. Υπολογίστε το HMAC-SHA256 αυτής της συμβολοσειράς με το μυστικό κλειδί API ως κλειδί και γράψτε το ως 64 πεζούς χαρακτήρες hex.

  4. Στείλτε την ως X-CEK-Signature μαζί με τις υπόλοιπες κεφαλίδες.

Κανονική συμβολοσειρά υπογραφής

string_to_sign = X-CEK-Timestamp + "\n" + METHOD + "\n" + path + "\n" + hex(sha256(body))
X-CEK-Signature = hex(HMAC-SHA256(key = API_SECRET, message = string_to_sign))  // 64 lowercase hex characters

Υπογράψτε ακριβώς τα byte που στέλνετε: σειριοποιήστε το JSON μία φορά και χρησιμοποιήστε την ίδια συμβολοσειρά για το hash και για το σώμα του αιτήματος.

Παράδειγμα (με πλασματικά στοιχεία)

Όλες οι τιμές είναι πλασματικές: μυστικό sk_test_EXAMPLE, χρονοσήμανση 1790000000. Χρησιμοποιήστε τις τιμές από τον δικό σας πίνακα.

Σώμα αιτήματος

{"merchant_order_id":"ORDER-1001","amount":"1500.00","bin":"540061"}

sha256(body)

3bb9ee51caacfb41bd7a8774f12ff861f97b1fd8cf416a00b5fa92618e8390e7

string_to_sign

1790000000
POST
/api/v4/orders
3bb9ee51caacfb41bd7a8774f12ff861f97b1fd8cf416a00b5fa92618e8390e7

X-CEK-Signature

f721cbcccb6ed8a249ed0e331133686fe5f9dfb227856bbd1b6ff9e0b2a8f348

Κώδικας παραδείγματος

Κάθε παράδειγμα αναζητά ένα BIN και δημιουργεί μια παραγγελία στο sandbox. Τα διαπιστευτήρια διαβάζονται από μεταβλητές περιβάλλοντος· μην βάζετε ποτέ το μυστικό στον κώδικα. Κάθε εκτέλεση χρησιμοποιεί νέο merchant_order_id.

#!/usr/bin/env bash
# export MERCHANT_API_KEY=... MERCHANT_API_SECRET=... MERCHANT_CODE=MRC-123456   (sandbox key)
set -euo pipefail

BASE_URL="https://www.manuellsolutions.com"

signed() {  # signed METHOD PATH [BODY]
  local method="$1" path="$2" body="${3:-}" timestamp sha string_to_sign signature
  timestamp="$(date +%s)"
  sha="$(printf '%s' "$body" | openssl dgst -sha256 -hex | awk '{print $NF}')"
  string_to_sign="$(printf '%s\n%s\n%s\n%s' "$timestamp" "$method" "${path%%\?*}" "$sha")"
  # the secret travels in the environment, not on the command line
  signature="$(printf '%s' "$string_to_sign" | python3 -c 'import hashlib,hmac,os,sys; print(hmac.new(os.environ["MERCHANT_API_SECRET"].encode(), sys.stdin.buffer.read(), hashlib.sha256).hexdigest())')"
  local args=(--silent --show-error --request "$method" "$BASE_URL$path"
    --header "X-CEK-Api-Key: $MERCHANT_API_KEY"
    --header "X-CEK-Environment: sandbox"
    --header "X-CEK-Merchant-Code: $MERCHANT_CODE"
    --header "X-CEK-Timestamp: $timestamp"
    --header "X-CEK-Signature: $signature")
  if [ -n "$body" ]; then args+=(--header "Content-Type: application/json" --data-binary "$body"); fi
  curl "${args[@]}"
  echo
}

# 1) BIN lookup: no body, so the hash is sha256("")
signed GET "/api/v4/bins/435509?amount=1500.00"

# 2) create an order; a new merchant_order_id for every order
ORDER_ID="ORDER-$(date +%s)-$(openssl rand -hex 4)"
signed POST "/api/v4/orders" "{\"merchant_order_id\":\"$ORDER_ID\",\"amount\":\"1500.00\",\"card\":{\"holder_name\":\"TEST CUSTOMER\",\"number\":\"4355090000044242\",\"expiry_month\":\"12\",\"expiry_year\":\"30\",\"cvv\":\"123\"}}"
<?php
$config = [
    'base'   => 'https://www.manuellsolutions.com',
    'key'    => getenv('MERCHANT_API_KEY'),
    'secret' => getenv('MERCHANT_API_SECRET'),
    'code'   => getenv('MERCHANT_CODE'),
    'env'    => 'sandbox',
];

function signedRequest(array $config, string $method, string $path, ?array $payload = null): array
{
    $method = strtoupper($method);
    $signingPath = explode('?', $path, 2)[0];
    // serialise once: the same bytes are hashed and sent
    $body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    $timestamp = (string) time();
    $stringToSign = implode("\n", [$timestamp, $method, $signingPath, hash('sha256', $body)]);
    $signature = hash_hmac('sha256', $stringToSign, $config['secret']);

    $headers = [
        'X-CEK-Api-Key: ' . $config['key'],
        'X-CEK-Environment: ' . $config['env'],
        'X-CEK-Merchant-Code: ' . $config['code'],
        'X-CEK-Timestamp: ' . $timestamp,
        'X-CEK-Signature: ' . $signature,
    ];
    if ($body !== '') {
        $headers[] = 'Content-Type: application/json';
    }

    $ch = curl_init($config['base'] . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 35, // an order can take up to 30 seconds
    ]);
    if ($body !== '') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($response === false) {
        throw new RuntimeException('No response; repeat the same request or read the order with GET.');
    }

    return [$status, json_decode((string) $response, true)];
}

// 1) BIN lookup: no body, so the hash is sha256("")
[$status, $bin] = signedRequest($config, 'GET', '/api/v4/bins/435509?amount=1500.00');
echo $status, ' ', $bin['data']['payment_mode'] ?? '', PHP_EOL;

// 2) create an order; a new merchant_order_id for every order
[$status, $order] = signedRequest($config, 'POST', '/api/v4/orders', [
    'merchant_order_id' => 'ORDER-' . time() . '-' . bin2hex(random_bytes(4)),
    'amount' => '1500.00',
    'card' => ['holder_name' => 'TEST CUSTOMER', 'number' => '4355090000044242', 'expiry_month' => '12', 'expiry_year' => '30', 'cvv' => '123'],
]);
echo $status, ' ', $order['data']['status'] ?? '', PHP_EOL;
const crypto = require("node:crypto");

const BASE_URL = "https://www.manuellsolutions.com";
const { MERCHANT_API_KEY, MERCHANT_API_SECRET, MERCHANT_CODE } = process.env;

async function signedRequest(method, path, payload) {
  method = method.toUpperCase();
  const signingPath = path.split("?", 1)[0];
  // serialise once: the same bytes are hashed and sent
  const body = payload === undefined ? "" : JSON.stringify(payload);
  const timestamp = String(Math.floor(Date.now() / 1000));
  const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
  const stringToSign = [timestamp, method, signingPath, bodyHash].join("\n");
  const signature = crypto.createHmac("sha256", MERCHANT_API_SECRET).update(stringToSign).digest("hex");

  const headers = {
    "X-CEK-Api-Key": MERCHANT_API_KEY,
    "X-CEK-Environment": "sandbox",
    "X-CEK-Merchant-Code": MERCHANT_CODE,
    "X-CEK-Timestamp": timestamp,
    "X-CEK-Signature": signature,
  };
  if (body !== "") headers["Content-Type"] = "application/json";

  // an order can take up to 30 seconds
  const res = await fetch(BASE_URL + path, { method, headers, body: body === "" ? undefined : body, signal: AbortSignal.timeout(35000) });
  return { status: res.status, body: JSON.parse(await res.text()) };
}

(async () => {
  // 1) BIN lookup: no body, so the hash is sha256("")
  const bin = await signedRequest("GET", "/api/v4/bins/435509?amount=1500.00");
  console.log(bin.status, bin.body.data?.payment_mode);

  // 2) create an order; a new merchant_order_id for every order
  const order = await signedRequest("POST", "/api/v4/orders", {
    merchant_order_id: `ORDER-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`,
    amount: "1500.00",
    card: { holder_name: "TEST CUSTOMER", number: "4355090000044242", expiry_month: "12", expiry_year: "30", cvv: "123" },
  });
  console.log(order.status, order.body.data?.status);
})();
import hashlib
import hmac
import json
import os
import time
import urllib.error
import urllib.request
import uuid

BASE_URL = "https://www.manuellsolutions.com"
API_KEY = os.environ["MERCHANT_API_KEY"]
API_SECRET = os.environ["MERCHANT_API_SECRET"]
MERCHANT_CODE = os.environ["MERCHANT_CODE"]


def signed_request(method, path, payload=None):
    method = method.upper()
    signing_path = path.split("?", 1)[0]
    # serialise once: the same bytes are hashed and sent
    body = json.dumps(payload, separators=(",", ":")) if payload is not None else ""
    timestamp = str(int(time.time()))
    string_to_sign = "\n".join([timestamp, method, signing_path, hashlib.sha256(body.encode()).hexdigest()])
    signature = hmac.new(API_SECRET.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()

    headers = {
        "X-CEK-Api-Key": API_KEY,
        "X-CEK-Environment": "sandbox",
        "X-CEK-Merchant-Code": MERCHANT_CODE,
        "X-CEK-Timestamp": timestamp,
        "X-CEK-Signature": signature,
    }
    if body:
        headers["Content-Type"] = "application/json"

    request = urllib.request.Request(BASE_URL + path, data=body.encode() or None, headers=headers, method=method)
    try:
        with urllib.request.urlopen(request, timeout=35) as response:  # an order can take up to 30 seconds
            return response.status, json.loads(response.read())
    except urllib.error.HTTPError as error:  # problem+json body
        return error.code, json.loads(error.read())


# 1) BIN lookup: no body, so the hash is sha256("")
status, bin_info = signed_request("GET", "/api/v4/bins/435509?amount=1500.00")
print(status, bin_info.get("data", {}).get("payment_mode"))

# 2) create an order; a new merchant_order_id for every order
status, order = signed_request("POST", "/api/v4/orders", {
    "merchant_order_id": f"ORDER-{int(time.time())}-{uuid.uuid4().hex[:8]}",
    "amount": "1500.00",
    "card": {"holder_name": "TEST CUSTOMER", "number": "4355090000044242", "expiry_month": "12", "expiry_year": "30", "cvv": "123"},
})
print(status, order.get("data", {}).get("status"))

Αναζήτηση BIN

GET/api/v4/bins/{bin}

Αναζητήστε το BIN μιας κάρτας

Καλέστε το μόλις ο πελάτης πληκτρολογήσει τα πρώτα 6 ψηφία. Η απάντηση είναι ένα στιγμιότυπο που σας βοηθά να επιλέξετε ανάμεσα στη δική σας φόρμα κάρτας και τη φιλοξενούμενη σελίδα· η τελική απόφαση λαμβάνεται κατά τη δημιουργία της παραγγελίας.

Παράμετροι

ΠεδίοΤύποςΥποχρεωτικόΠεριγραφή
binstring · pathΥποχρεωτικόΤα πρώτα έξι ψηφία του αριθμού της κάρτας.
amountstring · queryΥποχρεωτικόΠοσό παραγγελίας σε TRY με 2 δεκαδικά. Η καταλληλότητα αξιολογείται για αυτό το ποσό.

Απόκριση · 200

ΠεδίοΤύποςΠεριγραφή
binstringΤο BIN που ζητήσατε.
bank_namestring | nullΤράπεζα έκδοσης, ή null αν δεν είναι γνωστή.
networkstring | nullΔίκτυο κάρτας: visa, mastercard, amex, troy ή null.
card_typestring | nullcredit, debit, prepaid ή null.
card_programstring | nullΌνομα προγράμματος κάρτας ή null.
countrystring | nullΧώρα έκδοσης (ISO alpha-2) ή null.
domesticbooleantrue για κάρτα που εκδόθηκε στην Τουρκία.
payment_modestringdirect: συλλέξτε εσείς την κάρτα. hosted: χρησιμοποιήστε τη φιλοξενούμενη σελίδα.

Απόκριση · Παράδειγμα

{
  "data": {
    "bin": "435509",
    "bank_name": "Example Bank",
    "network": "visa",
    "card_type": "credit",
    "card_program": null,
    "country": "TR",
    "domestic": true,
    "payment_mode": "direct"
  }
}
  • Με direct, συλλέξτε όλα τα στοιχεία της κάρτας και δημιουργήστε την παραγγελία με card.
  • Με hosted, δημιουργήστε την παραγγελία μόνο με bin και οδηγήστε τον πελάτη στο hosted_url που επιστρέφεται.

Πιθανά σφάλματα

Δημιουργία παραγγελίας

POST/api/v4/orders

Δημιουργήστε παραγγελία

Ένα endpoint δημιουργεί την παραγγελία και στις δύο λειτουργίες. Το σώμα αποφασίζει: στείλτε card για παραγγελία direct ή μόνο bin για παραγγελία hosted. Αν στείλετε και τα δύο επιστρέφεται 422.

Παραγγελία direct (με κάρτα)

ΠεδίοΤύποςΥποχρεωτικόΠεριγραφή
merchant_order_idstringΥποχρεωτικόΗ αναφορά της παραγγελίας σας: 1–64 χαρακτήρες από A-Z a-z 0-9 . _ -, μοναδική ανά λογαριασμό και περιβάλλον.
amountstringΥποχρεωτικόΠοσό σε TRY με 2 δεκαδικά, π.χ. "1500.00".
card.holder_namestringΥποχρεωτικόΌνομα στην κάρτα, 2–120 χαρακτήρες.
card.numberstringΥποχρεωτικόΑριθμός κάρτας, 13–19 ψηφία, μόνο αριθμοί.
card.expiry_monthstringΥποχρεωτικόΜήνας λήξης, από "01" έως "12".
card.expiry_yearstringΥποχρεωτικόΈτος λήξης με 2 ψηφία, π.χ. "30".
card.cvvstringΥποχρεωτικόΚωδικός ασφαλείας, 3 ή 4 ψηφία.

Σώμα αιτήματος · Παράδειγμα

{
  "merchant_order_id": "ORDER-1001",
  "amount": "1500.00",
  "card": {
    "holder_name": "TEST CUSTOMER",
    "number": "4355090000044242",
    "expiry_month": "12",
    "expiry_year": "30",
    "cvv": "123"
  }
}

Πιθανά σφάλματα

Παραγγελία hosted (μόνο BIN)

ΠεδίοΤύποςΥποχρεωτικόΠεριγραφή
merchant_order_idstringΥποχρεωτικόΗ αναφορά της παραγγελίας σας: 1–64 χαρακτήρες από A-Z a-z 0-9 . _ -, μοναδική ανά λογαριασμό και περιβάλλον.
amountstringΥποχρεωτικόΠοσό σε TRY με 2 δεκαδικά, π.χ. "1500.00".
binstringΥποχρεωτικόΤα πρώτα έξι ψηφία του αριθμού της κάρτας.

Σώμα αιτήματος · Παράδειγμα

{
  "merchant_order_id": "ORDER-1002",
  "amount": "1500.00",
  "bin": "540061"
}

Πιθανά σφάλματα

Κανόνες

  • Το merchant_order_id πρέπει να είναι μοναδικό ανά λογαριασμό και περιβάλλον. Είναι η μόνη προστασία από διπλότυπα.
  • Η επανάληψη ενός αιτήματος με το ίδιο merchant_order_id και το ίδιο περιεχόμενο δεν ξεκινά νέα πληρωμή και επιστρέφει την παραγγελία όπως είναι τώρα. Διαφορετικό περιεχόμενο επιστρέφει 409 με κωδικό 0055503.
  • Ένα πεδίο που δεν υπάρχει στο σχήμα επιστρέφει 422 με κωδικό 1000001.
  • Σε απόκριση 4xx δεν έχει εισπραχθεί πληρωμή. Χρησιμοποιήστε νέο merchant_order_id για την επόμενη προσπάθεια. Και μια παραγγελία failed είναι τελική.
  • Μετά από απόκριση 5xx ή λήξη χρόνου, η πληρωμή μπορεί να έχει ήδη ξεκινήσει. Μη δημιουργήσετε νέα παραγγελία: επαναλάβετε το ίδιο αίτημα αμετάβλητο ή διαβάστε την παραγγελία με GET. Αφήστε τουλάχιστον 30 δευτερόλεπτα πριν λήξει ο χρόνος.
  • Αν το BIN είναι εκείνη τη στιγμή hosted, ακόμη και ένα αίτημα με card επιστρέφει requires_hosted και τα στοιχεία της κάρτας δεν χρησιμοποιούνται.

Το αντικείμενο παραγγελίας

Οι κλήσεις δημιουργίας και ανάγνωσης επιστρέφουν την παραγγελία μέσα στο data.

ΠεδίοΤύποςΠεριγραφή
order_codestringΑριθμός παραγγελίας, 9 ψηφία ως συμβολοσειρά. Είναι η τιμή που έρχεται στη διεύθυνση επιστροφής.
merchant_order_idstringΤο δικό σας merchant_order_id.
statusstringrequires_action, requires_hosted, pending, paid ή failed.
amountstringΠοσό παραγγελίας, 2 δεκαδικά.
currencystringΠάντα TRY.
redirect_urlstring | nullΔιεύθυνση 3-D Secure. Συμπληρωμένο όσο η κατάσταση είναι requires_action.
hosted_urlstring | nullΔιεύθυνση της φιλοξενούμενης σελίδας. Συμπληρωμένο όσο η κατάσταση είναι requires_hosted.
hosted_expires_atstring | nullΗ στιγμή που το hosted_url παύει να λειτουργεί (ISO 8601, UTC).
failure_reason_codestring | nullΚωδικός αιτίας για παραγγελία failed· βλ. Λόγοι απόρριψης. Μπορεί να είναι null αν δεν έγινε ποτέ απόπειρα πληρωμής.
failure_messagestring | nullΣύντομο κείμενο για τον πελάτη σχετικά με την αιτία. Είναι πάντα στα τουρκικά· διακλαδώστε με βάση τον κωδικό.
cardobject | nullΠερίληψη κάρτας (holder_name, bin, last_four, network, program, issuer_bank_name). Διαθέσιμη αμέσως στο direct· στο hosted όταν γίνει γνωστό το αποτέλεσμα.
created_atstringΏρα δημιουργίας (ISO 8601, +00:00).
completed_atstring | nullΏρα ολοκλήρωσης ή null όσο η παραγγελία είναι ανοιχτή.

Απόκριση · Παράδειγμα

{
  "data": {
    "order_code": "123456789",
    "merchant_order_id": "ORDER-1001",
    "status": "requires_action",
    "amount": "1500.00",
    "currency": "TRY",
    "redirect_url": "https://bank.example.com/3ds/123456789",
    "hosted_url": null,
    "hosted_expires_at": null,
    "failure_reason_code": null,
    "failure_message": null,
    "card": {
      "holder_name": "TEST CUSTOMER",
      "bin": "435509",
      "last_four": "4242",
      "network": "visa",
      "program": null,
      "issuer_bank_name": "Example Bank"
    },
    "created_at": "2026-10-13T10:15:02+00:00",
    "completed_at": null
  }
}

requires_hosted

{
  "data": {
    "order_code": "123456789",
    "merchant_order_id": "ORDER-1001",
    "status": "requires_hosted",
    "amount": "1500.00",
    "currency": "TRY",
    "redirect_url": null,
    "hosted_url": "https://pay.example.com/h/123456789",
    "hosted_expires_at": "2026-10-13T10:30:02+00:00",
    "failure_reason_code": null,
    "failure_message": null,
    "card": null,
    "created_at": "2026-10-13T10:15:02+00:00",
    "completed_at": null
  }
}

Καταστάσεις και χρόνοι

ΠεδίοΤελική;Περιγραφή
requires_actionΌχιΟδηγήστε τον πελάτη στο redirect_url για 3-D Secure.
requires_hostedΌχιΟδηγήστε τον πελάτη στο hosted_url.
pendingΌχιΗ τράπεζα δεν έχει απαντήσει ακόμη ή ο πελάτης έφυγε από την οθόνη. Περιμένετε το webhook.
paidΝαιΗ πληρωμή εισπράχθηκε.
failedΝαιΗ πληρωμή δεν ολοκληρώθηκε. Το failure_reason_code εξηγεί γιατί.

Ο πελάτης έχει 10 λεπτά για το 3-D Secure. Η φιλοξενούμενη σελίδα λειτουργεί μέχρι το hosted_expires_at. Μια πληρωμή στην οποία η τράπεζα δεν απαντά ποτέ γίνεται paid ή failed μέσα σε 24 ώρες και μια ληγμένη πληρωμή γίνεται failed.

Σπάνια, μια παραγγελία failed μπορεί να γίνει αργότερα paid. Θεωρήστε το paid ως την αλήθεια.

Ανάγνωση παραγγελίας

GET/api/v4/orders/{merchantOrderId}

Διαβάστε μια παραγγελία

Επιστρέφει το ίδιο αντικείμενο με τη δημιουργία, χωρίς σώμα αιτήματος. Το redirect_url επιστρέφεται μέχρι να λήξει το παράθυρο 3-D Secure και το hosted_url μέχρι το hosted_expires_at.

Παράμετροι

ΠεδίοΤύποςΥποχρεωτικόΠεριγραφή
merchantOrderIdstring · pathΥποχρεωτικόΤο merchant_order_id που χρησιμοποιήσατε κατά τη δημιουργία της παραγγελίας.

Το webhook είναι το κύριο κανάλι. Αν καθυστερεί, διαβάστε την παραγγελία το πολύ κάθε 30 δευτερόλεπτα.

Πιθανά σφάλματα

3-D Secure και η φιλοξενούμενη σελίδα

Μετά τη δημιουργία της παραγγελίας ο πελάτης ολοκληρώνει την πληρωμή σε σελίδα που δεν είναι δική σας. Στείλτε τον εκεί με κανονική ανακατεύθυνση ολόκληρης σελίδας.

3-D Secure (`requires_action`)

  1. Ανακατευθύνετε το πρόγραμμα περιήγησης του πελάτη στο redirect_url.

  2. Ο πελάτης επιβεβαιώνει την πληρωμή στην τράπεζά του.

  3. Όταν η πληρωμή γίνει τελική, ο πελάτης οδηγείται στη διεύθυνση επιστροφής και εσείς λαμβάνετε το webhook.

Φιλοξενούμενη σελίδα (`requires_hosted`)

  1. Οδηγήστε τον πελάτη στο hosted_url. Η ανακατεύθυνση ολόκληρης σελίδας λειτουργεί πάντα· η εμφάνιση σε modal πλαίσιο εξαρτάται από την άδεια της σελίδας, γι' αυτό κρατήστε την ανακατεύθυνση ως εφεδρική λύση.

  2. Ο πελάτης εισάγει την κάρτα και ολοκληρώνει το 3-D Secure σε εκείνη τη σελίδα. Μια αποτυχημένη προσπάθεια δεν κλείνει την παραγγελία· ο πελάτης μπορεί να ξαναδοκιμάσει εκεί.

  3. Αν ο πελάτης κλείσει τη σελίδα, το ίδιο hosted_url την ανοίγει ξανά μέχρι το hosted_expires_at.

  4. Όταν τελειώσει η πληρωμή, ολόκληρη η σελίδα οδηγείται στη διεύθυνση επιστροφής. Επιβεβαιώστε το αποτέλεσμα με το webhook.

Διεύθυνση επιστροφής

Όταν μια παραγγελία γίνει paid ή failed, ο πελάτης οδηγείται στην αντίστοιχη διεύθυνση επιστροφής που ορίσατε στον πίνακα. Αν έχει οριστεί διεύθυνση webhook, στη διεύθυνση προστίθενται οι παρακάτω υπογεγραμμένες παράμετροι.

ΠεδίοΤύποςΠεριγραφή
order_codestringΟ 9ψήφιος αριθμός παραγγελίας. Η διεύθυνση δεν περιέχει merchant_order_id· αντιστοιχίστε την παραγγελία με αυτή την τιμή.
statusstringpaid ή failed.
failure_codestringΚωδικός αιτίας, μόνο για παραγγελία failed που έχει αιτία. Διαφορετικά η παράμετρος λείπει.
tsintegerΧρόνος Unix σε δευτερόλεπτα κατά τη δημιουργία της διεύθυνσης.
key_versionintegerΈκδοση του μυστικού σας. Αυξάνεται κάθε φορά που το ανανεώνετε.
sigstringΥπογραφή, 64 πεζοί χαρακτήρες hex.

Κανονική συμβολοσειρά υπογραφής

sig = hex(HMAC-SHA256(key = API_SECRET, message = ts + "." + order_code + "." + status + "." + failure_code))
  • Επαληθεύστε το sig πριν εμπιστευτείτε τις παραμέτρους. Αν λείπει το failure_code, βάλτε κενή συμβολοσειρά στη θέση του· η τελεία παραμένει.
  • Απορρίψτε ένα ts παλαιότερο από 10 λεπτά.
  • Η επιστροφή δείχνει μόνο πού βρίσκεται ο πελάτης. Το αποτέλεσμα είναι τελικό όταν φτάσει το webhook ή το επιβεβαιώσει το GET /api/v4/orders/{merchantOrderId}.
  • Αν δεν έχει οριστεί διεύθυνση webhook, η διεύθυνση ανοίγει χωρίς αυτές τις παραμέτρους.

Παράδειγμα

https://shop.example.com/payment/success?order_code=123456789&status=failed&failure_code=insufficient_funds&ts=1790000000&key_version=1&sig=a6d0ee5e1774b836d04836d4046fff37e719904d9fea69e28a558e619743c4c2

Webhook

Τα αποτελέσματα παραδίδονται ως υπογεγραμμένα αιτήματα JSON POST στη διεύθυνση webhook HTTPS του περιβάλλοντος.

ΣυμβάνΠότε αποστέλλεται
checkout.order.succeededΗ παραγγελία είναι paid.
checkout.order.failedΗ παραγγελία είναι failed.

Κεφαλίδα

ΚεφαλίδαΠεριγραφή
X-CEK-TimestampΧρόνος Unix σε δευτερόλεπτα. Απορρίψτε απόκλιση πάνω από 300 δευτερόλεπτα.
X-CEK-SignatureΥπογραφή: hex του HMAC-SHA256 της χρονοσήμανσης, μιας τελείας και του ακατέργαστου σώματος, με το μυστικό κλειδί API ως κλειδί.
X-CEK-Event-IdUUID του συμβάντος. Ίδιο με το event_id στο σώμα και δεν αλλάζει στις επαναλήψεις.
X-CEK-Environmentsandbox ή live.

Επαληθεύστε κάθε ειδοποίηση

  1. Διαβάστε το ακατέργαστο σώμα πριν αναλύσετε το JSON.

  2. Υπολογίστε την υπογραφή πάνω στο X-CEK-Timestamp, μια τελεία και το ακατέργαστο σώμα και συγκρίνετέ την με το X-CEK-Signature σε σταθερό χρόνο.

  3. Απορρίψτε το αίτημα αν η χρονοσήμανση διαφέρει από το ρολόι σας περισσότερο από 300 δευτερόλεπτα.

  4. Αφαιρέστε τα διπλότυπα με το X-CEK-Event-Id. Η παράδοση γίνεται τουλάχιστον μία φορά και μπορεί να είναι εκτός σειράς. Αν έχετε δει checkout.order.succeeded για μια παραγγελία, αγνοήστε κάθε checkout.order.failed γι' αυτήν.

  5. Απαντήστε με οποιοδήποτε 2xx μέσα σε 10 δευτερόλεπτα (3 για τη σύνδεση). Οι ανακατευθύνσεις (3xx), τα 4xx, 5xx και οι λήξεις χρόνου θεωρούνται αποτυχίες και επαναλαμβάνονται.

Παραδείγματα επαλήθευσης υπογραφής

<?php
// Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>".
function isValidDirectWebhook(string $rawBody, string $timestamp, string $signature, string $secret): bool
{
    if (preg_match('/^[0-9]{10}$/D', $timestamp) !== 1 || preg_match('/^[0-9a-f]{64}$/D', $signature) !== 1
        || abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    return hash_equals(hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret), $signature);
}

$rawBody = file_get_contents('php://input');
if (!isValidDirectWebhook($rawBody, $_SERVER['HTTP_X_CEK_TIMESTAMP'] ?? '', $_SERVER['HTTP_X_CEK_SIGNATURE'] ?? '', getenv('MERCHANT_API_SECRET'))) {
    http_response_code(401);
    exit;
}
// Deduplicate with X-CEK-Event-Id, save the event, then answer 2xx.
http_response_code(204);
const crypto = require("node:crypto");

// Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>".
// rawBody must be the untouched request bytes (Buffer), not re-serialised JSON.
function isValidDirectWebhook(rawBody, timestamp, signature, secret) {
  if (!/^[0-9]{10}$/.test(timestamp) || !/^[0-9a-f]{64}$/.test(signature)
      || Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody.toString("utf8")).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Then deduplicate with X-CEK-Event-Id, save the event and answer 2xx.
module.exports = { isValidDirectWebhook };
import hashlib
import hmac
import re
import time


def is_valid_direct_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    """Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>"."""
    if (not re.fullmatch(r"[0-9]{10}", timestamp) or not re.fullmatch(r"[0-9a-f]{64}", signature)
            or abs(int(time.time()) - int(timestamp)) > 300):
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


# Then deduplicate with X-CEK-Event-Id, save the event and answer 2xx.

Επαναλήψεις

Μια αποτυχημένη παράδοση επαναλαμβάνεται μετά από τις παρακάτω αναμονές, μετρημένες από την προηγούμενη αποτυχία, έως 11 προσπάθειες συνολικά.

ΠροσπάθειαΑναμονή πριν
1—
260 s (1 min)
3300 s (5 min)
4900 s (15 min)
51800 s (30 min)
63600 s (1 h)
710800 s (3 h)
821600 s (6 h)
943200 s (12 h)
1086400 s (24 h)
11172800 s (48 h)

Σώμα

Το σώμα περιέχει schema_version (3), event_id, event_type, ένα αντικείμενο order, ένα αντικείμενο merchant και την περίληψη της κάρτας στο payment_details.card. Ένα συμβάν failed προσθέτει failure_code, failure_reason_code και failure_message. Αγνοήστε τα πεδία που δεν χρησιμοποιείτε· μπορεί να προστεθούν νέα.

Σώμα αιτήματος · Παράδειγμα

{
  "schema_version": 3,
  "event_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6",
  "event_type": "checkout.order.failed",
  "order": {
    "code": "123456789",
    "merchant_order_id": "ORDER-1001",
    "status": "failed",
    "amount": "1500.00",
    "currency": "TRY"
  },
  "merchant": {
    "code": "MRC-123456"
  },
  "payment_details": {
    "card": {
      "holder_name": "TEST CUSTOMER",
      "bin": "435509",
      "last_four": "4242",
      "network": "visa",
      "program": null,
      "issuer_bank_code": null,
      "issuer_bank_name": "Example Bank",
      "installment_count": 1,
      "three_d": true
    }
  },
  "failure_code": "payment_declined",
  "failure_reason_code": "insufficient_funds",
  "failure_message": "Kart bakiyeniz veya limitiniz bu ödeme için yeterli değil."
}

Λόγοι απόρριψης

Το failure_reason_code είναι ένας από αυτούς τους κωδικούς. Βασίστε τη λογική σας στον κωδικό· το failure_message είναι κείμενο στα τουρκικά για τον πελάτη.

ΚωδικόςΠεριγραφή
payment_declinedΗ τράπεζα έκδοσης δεν ενέκρινε την πληρωμή.
do_not_honorΗ τράπεζα απέρριψε χωρίς συγκεκριμένο λόγο.
insufficient_fundsΤο υπόλοιπο ή το όριο της κάρτας δεν επαρκεί για αυτή την πληρωμή.
card_limit_reachedΗ κάρτα έφτασε το όριο συναλλαγών της.
card_details_invalidΟ αριθμός κάρτας δεν έγινε δεκτός.
card_expiry_invalidΗ ημερομηνία λήξης δεν είναι έγκυρη.
card_security_code_invalidΟ κωδικός ασφαλείας (CVV) είναι λάθος.
card_not_activeΗ κάρτα δεν είναι ενεργοποιημένη για πληρωμές.
card_brand_not_supportedΑυτή η μάρκα κάρτας δεν υποστηρίζεται για αυτή την πληρωμή.
three_ds_failedΤο 3-D Secure δεν μπόρεσε να ολοκληρωθεί.
security_declinedΈνας έλεγχος ασφαλείας σταμάτησε την πληρωμή. Πείτε στον πελάτη μόνο ότι η τράπεζα δεν την ενέκρινε.
payment_not_permittedΑυτή η κάρτα δεν μπορεί να χρησιμοποιηθεί για αυτή την πληρωμή.
amount_not_allowedΤο ποσό είναι εκτός του εύρους που δέχεται αυτή η κάρτα.
currency_not_supportedΤο νόμισμα δεν υποστηρίζεται για αυτή την πληρωμή.
payment_details_rejectedΤα στοιχεία πληρωμής απορρίφθηκαν.
duplicate_paymentΦαίνεται ότι η ίδια πληρωμή έχει ήδη εισπραχθεί.
cancelled_by_customerΟ πελάτης ακύρωσε πριν ολοκληρωθεί η πληρωμή.
payment_timed_outΟ χρόνος της πληρωμής έληξε.
bank_unavailableΔεν είναι δυνατή η επικοινωνία με την τράπεζα αυτή τη στιγμή. Δοκιμάστε ξανά σύντομα.
bank_requested_retryΗ τράπεζα ζήτησε επανάληψη της πληρωμής.
provider_unavailableΗ πληρωμή δεν μπόρεσε να ολοκληρωθεί αυτή τη στιγμή. Δοκιμάστε ξανά σύντομα.

Η λίστα μπορεί να επεκταθεί. Αντιμετωπίστε έναν κωδικό που δεν γνωρίζετε όπως το payment_declined. Στο webhook, το security_declined αναφέρεται ως payment_declined στο failure_code.

Κωδικοί σφαλμάτων

Τα σφάλματα χρησιμοποιούν τη μορφή application/problem+json. Διακλαδώστε με βάση το code· τα title και detail είναι για ανθρώπους.

ΠεδίοΤύποςΠεριγραφή
type, title, status, detailstring · integerΤυπικά πεδία του problem.
instancestringΗ διαδρομή του αιτήματος.
codestringΟ 7ψήφιος κωδικός σφάλματος από τον παρακάτω πίνακα.
correlation_idstringΑναφέρετέ το όταν επικοινωνείτε με την υποστήριξη. Αποστέλλεται και ως X-Correlation-Id.
metadataobjectdeveloper_detail, retry_class και audit_event: πρόσθετες πληροφορίες για αποσφαλμάτωση.
amount_limitobjectΜόνο στο 0055235: min και max του επιτρεπόμενου ποσού, ή null για όριο όγκου.
errorsobjectΜόνο στο 1000001: τα πεδία που απέτυχαν στον έλεγχο.

application/problem+json

{
  "type": "https://www.manuellsolutions.com/problems/0055233",
  "title": "BIN not recognised",
  "status": 409,
  "detail": "No card range matches this BIN.",
  "instance": "/api/v4/bins/999999",
  "code": "0055233",
  "correlation_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6",
  "metadata": {
    "developer_detail": "bin_not_found",
    "retry_class": "no_retry",
    "audit_event": "bin_lookup_rejected"
  }
}

Οι αποκρίσεις σφάλματος φέρουν επίσης X-Correlation-Id, X-RateLimit-Limit, X-RateLimit-Remaining και Content-Language· το 429 προσθέτει Retry-After.

Οι έλεγχοι εκτελούνται με αυτή τη σειρά: σχήμα (1000001), ποσό (0055235), BIN (0055233, 0055230), κάρτα (0055234).

ΚωδικόςHTTPΣημασία
0055501403Η λίστα επιτρεπόμενων IP είναι κενή ή το αίτημα προέρχεται από διεύθυνση που δεν περιλαμβάνεται.
0055502401 / 403Λάθος κλειδί, περιβάλλον ή κωδικός εμπόρου, ανενεργός λογαριασμός ή μη ανοιγμένη πρόσβαση στο Direct API.
0055504401Η χρονοσήμανση δεν έχει 10 ψηφία ή αποκλίνει πάνω από 300 δευτερόλεπτα, ή η υπογραφή δεν είναι 64 πεζοί χαρακτήρες hex.
0055505401Η υπογραφή δεν μπόρεσε να επαληθευτεί.
1000001422Το σώμα δεν ταιριάζει με το σχήμα: πεδίο που λείπει, λάθος ή άγνωστο, ή card μαζί με bin. Το errors απαριθμεί τα πεδία.
1000005429Υπέρβαση ορίου ρυθμού. Περιμένετε το διάστημα του Retry-After.
0055001409Ο λογαριασμός δεν είναι ακόμη έτοιμος να δέχεται πληρωμές· λείπει ένα βήμα ρύθμισης.
0055002403Οι πληρωμές με κάρτα είναι απενεργοποιημένες για τον λογαριασμό.
0055235422Το ποσό δεν είναι έγκυρο ή είναι εκτός ορίων (το amount_limit είναι συμπληρωμένο), ή επιτεύχθηκε ημερήσιο, εβδομαδιαίο ή μηνιαίο όριο όγκου (το amount_limit είναι null).
0055230409Δεν υπάρχει διαθέσιμο κανάλι πληρωμής για αυτό το BIN.
0055233409Το BIN δεν αναγνωρίστηκε.
0055210422Το bin δεν είναι ακριβώς έξι ψηφία.
0055234409Ο αριθμός κάρτας ή η ημερομηνία λήξης δεν είναι έγκυρα.
0055221409Συμπληρώθηκε το ημερήσιο όριο προσπαθειών ή ποσού αυτής της κάρτας.
0055231409Αυτή η κάρτα δεν μπορεί να χρεωθεί αυτή τη στιγμή.
0055503409Το ίδιο merchant_order_id χρησιμοποιήθηκε με διαφορετικό περιεχόμενο ή το πρώτο αίτημα εξακολουθεί να επεξεργάζεται.
0055402404Δεν υπάρχει παραγγελία με αυτό το merchant_order_id.
0055404409Μόνο για το endpoint αποτελέσματος sandbox: η παραγγελία δεν είναι ούτε pending ούτε requires_hosted.

Sandbox

Το sandbox χρησιμοποιεί τα ίδια endpoints με κλειδί sandbox και X-CEK-Environment: sandbox. Δεν μετακινούνται πραγματικά χρήματα.

  • Το X-CEK-Merchant-Code είναι υποχρεωτικό στο sandbox. Χρησιμοποιούνται οι διευθύνσεις webhook και επιστροφής του περιβάλλοντος sandbox.
  • Χρησιμοποιήστε οποιαδήποτε μελλοντική ημερομηνία λήξης και οποιοδήποτε CVV 3 ψηφίων.
  • Τα όρια όγκου μετρούν μόνο παραγγελίες sandbox.
BINHTTPΚωδικός αποτελέσματος
435509200direct
540061200hosted
9792104090055230
οποιοδήποτε άλλο BIN4090055233
Αριθμός κάρταςΣυμπεριφορά
4355090000044242requires_action. Εγκρίνετε τη δοκιμαστική οθόνη 3-D Secure για να γίνει paid.
4355090000005151requires_action. Μετά το 3-D Secure η παραγγελία γίνεται failed με insufficient_funds.
4355090000089999requires_action. Μετά το 3-D Secure η παραγγελία μένει pending· κλείστε την με το endpoint αποτελέσματος sandbox.
4355090000040000Γίνεται αμέσως failed με payment_declined, πριν το 3-D Secure.
οποιαδήποτε άλλη έγκυρη κάρταrequires_action. Στη δοκιμαστική οθόνη 3-D Secure επιλέγετε το αποτέλεσμα: επιτυχία δίνει paid, αποτυχία failed (payment_declined), λήξη χρόνου αφήνει pending.

Ένας μη έγκυρος αριθμός κάρτας (Luhn) ή μια ημερομηνία λήξης στο παρελθόν επιστρέφει 409 με κωδικό 0055234. Μια παραγγελία hosted χρησιμοποιεί το BIN 540061· στη δοκιμαστική της σελίδα επιλέγετε επιτυχία (paid) ή αποτυχία (failed με insufficient_funds). Όσο βρίσκεται ακόμη σε requires_hosted, μπορείτε να την κλείσετε και με το endpoint αποτελέσματος sandbox.

POST/api/v4/sandbox/orders/{merchantOrderId}/result

Ορίστε το αποτέλεσμα μιας παραγγελίας

Κλείνει μια παραγγελία sandbox που είναι pending ή, σε παραγγελία hosted, περιμένει ακόμη τη σελίδα hosted (requires_hosted). Το webhook και η διεύθυνση επιστροφής λειτουργούν όπως στο live.

ΠεδίοΤύποςΥποχρεωτικόΠεριγραφή
resultstringΥποχρεωτικόpaid ή failed (μια αποτυχημένη παραγγελία παίρνει την αιτία payment_declined).

Σώμα αιτήματος · Παράδειγμα

{
  "result": "paid"
}

Πιθανά σφάλματα

Μιλήστε με τον υπεύθυνο λογαριασμού σας για το live τερματικό και την τελική δοκιμή από άκρο σε άκρο. Επικοινωνία