API sənədləşməsi

E-Xezine vasitəsilə ödəniş qəbulu üçün developerə lazım olan hər şey: ödənişlərin yaradılması, vebhuklar, geri qaytarmalar, quraşdırılmış forma.

22.09.2026 tarixli redaksiya, API versiyası 2026-09-22

2026-09-22 versiyalı API üçün sənədləşmə. Baza ünvanı — https://exezine.az/api/v2, maşın üçün təsvir — openapi.json. Bütün metodlar üzrə interaktiv arayış: açmaq.

Necə işləyir

  1. Serveriniz ödəniş yaradır: POST /payments.
  2. Cavabda checkout_url gəlir — bu bizim ödəniş səhifəmizdir.
  3. Müştərini həmin ünvana yönləndirirsiniz (və ya öz saytınızda çərçivədə göstərirsiniz).
  4. Ödəniş bitəndə sizə vebhuk göndəririk: payment.succeeded.
  5. Müştərini sizin return_url ünvanınıza qaytarırıq.

Siz yalnız E-Xezine ilə inteqrasiya edirsiniz. Ödənişi hansı ekvayrinq bankın emal etməsi bizim daxili məsələmizdir və istənilən an dəyişə bilər: ünvanlar, cavabın formatı və vebhuklar dəyişmir, kodunuz bunu hiss etmir.

Açarlar və avtorizasiya

Açarlar şəxsi kabinetdə, «API və vebhuklar» bölməsində verilir.

AçarGörünüşüNə edir
Canlıexz_live_…Real pul
Testexz_test_…Qum qutusu, pul yoxdur

Açar yaradılarkən bir dəfə göstərilir. İtiribsinizsə — yenisini buraxın, köhnəsini ləğv edin. Hər sorğuda göndərilir:

Authorization: Bearer exz_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Test və canlı mühitlər tam ayrıdır: test açarı ilə canlı ödəniş tapılmayacaq (404 qayıdacaq) və əksinə.

Açar — pula girişdir. O, yalnız sizin serverinizdə yaşayır. Heç vaxt onu sayt kodunda, mobil tətbiqdə və ya repozitoriyada saxlamayın.

Ümumi qaydalar

Məbləğlər — valyutanın minimal vahidlərində tam ədədlərdir. 10 manat = 1000, 25.50 = 2550. API-də ümumiyyətlə onluq kəsrlər yoxdur — yuvarlaqlaşdırma səbəbindən bir qəpiklik səhv mümkün deyil.

Vaxt — RFC 3339 sətirləri: 2026-09-22T18:30:00+04:00. Kodlaşdırma — UTF-8, sorğunun gövdəsi JSON, Content-Type: application/json başlığı ilə. Hər cavabda request_id var — dəstəyə yazanda onu göstərin.

Xətalar

Həmişə eyni zərf:

{
  "error": {
    "type": "invalid_request_error",
    "code": "amount_invalid",
    "message": "Məbləğ sıfırdan böyük tam ədəd olmalıdır",
    "param": "amount"
  },
  "request_id": "req_4f2c9a1b8e0d3a71"
}

message deyil, code əsas götürün: o sabitdir, mətn isə dəyişə bilər və Accept-Language: ru | az | en başlığından asılıdır.

HTTPtypeNə vaxt
400invalid_request_errorYanlış JSON
401authentication_errorAçar yoxdur, yanlışdır və ya ləğv edilib
403authentication_errorMağaza üçün API söndürülüb
404invalid_request_errorObyekt tapılmadı
409invalid_request_errorKonflikt: idempotentlik, uyğun olmayan status
422invalid_request_errorSahənin dəyəri uyğun deyil
429rate_limit_errorHəddindən çox sorğu
5xxapi_errorBizim tərəf və ya şlüz

Sorğu tezliyinin limiti

Susmaya görə hər açar üçün dəqiqədə 120 sorğu. Cavabda həmişə:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118

Aşıldıqda — 429 və saniyələrlə Retry-After. Göstərilən qədər gözləyin və təkrarlayın; dövrə ilə döyəcləməyin.

İdempotentlik

Ödəniş, geri qaytarma və ya link yaradarkən başlıq göndərin:

Idempotency-Key: sizin istənilən unikal sətriniz
  • Eyni açar və eyni gövdə → birinci nəticəni və Idempotent-Replayed: true başlığını qaytarırıq. İkiqat silinmə olmayacaq.
  • Eyni açar, başqa gövdə → 409 idempotency_conflict.
  • Açarlar bir sutka yaşayır.

Beləliklə şəbəkə qırılandan sonra sorğunu təkrarlamaq təhlükəsizdir: heç vaxt bir ödəniş əvəzinə iki ödəniş yaranmayacaq.

Ödəniş yaratmaq

YaratmaqPOST /api/v2/payments
{
  "amount": 4990,
  "currency": "AZN",
  "order_id": "1024",
  "description": "Sifariş №1024",
  "method": "card",
  "return_url": "https://shop.az/thanks",
  "customer": {
    "email": "buyer@example.com",
    "name": "Aysel Məmmədova",
    "phone": "+994501234567",
    "ip": "85.132.1.1",
    "country": "AZ",
    "city": "Baku",
    "address": "Nizami 10",
    "zip": "AZ1000"
  },
  "metadata": { "cart": "17", "source": "mobile" },
  "locale": "az"
}
SahəVacibTəsvir
amountbəliTam ədəd, minimal vahidlər
currencyxeyrSusmaya görə AZN; siyahını biz qoşuruq
order_idxeyrSizin sifariş nömrəniz. Sonra ona görə axtarmaq olar
descriptionxeyr200 simvola qədər, müştəriyə görünür
methodxeyrcard (susmaya görə) və ya sbp
return_urlxeyrMüştərini hara qaytarmalı. İctimai https://
customer.*xeyrNə qədər dolğun olsa, 3-D Secure imtinaları bir o qədər az olar
metadataxeyr20 sahəyə və 4 KB-a qədər. Olduğu kimi qaytarırıq
localexeyraz / ru / en — ödəniş səhifəsinin dili

customer haqqında. Emitent banklar 3-D Secure 2-də riski məhz bu məlumatlara görə qiymətləndirir. Heç olmasa email, ipcountry göndərin — bu, imtinaların payını nəzərəçarpacaq dərəcədə azaldır.

SBP haqqında. method: "sbp" yalnız currency: "RUB" ilə işləyir. Başqa valyutada 422 method_not_available qayıdacaq.

Cavab 201

{
  "id": "EXZ-20260922-7FD54BA1D8",
  "object": "payment",
  "livemode": true,
  "status": "pending",
  "amount": 4990,
  "currency": "AZN",
  "amount_refunded": 0,
  "fee": 125,
  "net": 4865,
  "charged": null,
  "order_id": "1024",
  "description": "Sifariş №1024",
  "metadata": { "cart": "17" },
  "method": "card",
  "customer": { "email": "buyer@example.com", "name": "Aysel", "phone": "" },
  "card": null,
  "failure": null,
  "checkout_url": "https://exezine.az/checkout/EXZ-20260922-7FD54BA1D8",
  "return_url": "https://shop.az/thanks",
  "source": "api",
  "created_at": "2026-09-22T18:30:00+04:00",
  "paid_at": null,
  "request_id": "req_c0ad2cf33ed31637"
}

id — bizdəki ödənişin identifikatorudur. Onu sifarişin yanında saxlayın.

SahəMənası
livemodetrue — canlı ödəniş, false — qum qutusu
feeBu ödəniş üzrə bizim komissiyamız
netKomissiyadan sonra sizə nə qədər çatacaq
chargedSilinmə başqa valyutadadırsa — {"amount":…, "currency":"…"}, əks halda null
amount_refundedArtıq nə qədər geri qaytarılıb
sourceÖdəniş haradan: api, link, form, insales
cardÖdənişdən sonra: {"brand":"visa","last4":"4242","country":"AZ"}
paid_atÖdəniş anı; ondan əvvəl — null

Statuslar

StatusMənası
pendingYaradılıb, müştəri hələ ödəməyib
succeededÖdənilib
failedİmtina, bax failure
expiredÖdəniş müddəti bitib
refundedTam geri qaytarılıb
partially_refundedQismən geri qaytarılıb
reviewBizdə yoxlanışdadır

Yekun statuslar: succeeded, failed, expired, refunded, partially_refunded.

İmtinanın səbəbi

"failure": { "code": "insufficient_funds", "message": "Kartda kifayət qədər vəsait yoxdur" }
codeMüştəriyə nə demək
authentication_failedBank 3-D Secure təsdiqləmədi — yenidən cəhd edin
insufficient_fundsKifayət qədər vəsait yoxdur
card_expiredKartın etibarlılıq müddəti bitib
limit_exceededKart limiti aşılıb
card_declinedBank əməliyyatı rədd etdi
expiredÖdəniş müddəti bitib, yeni ödəniş yaradın

Statusu öyrənmək

Ödənişin statusuGET /api/v2/payments/{id}
curl -H "Authorization: Bearer $KEY" \
  https://exezine.az/api/v2/payments/EXZ-20260922-7FD54BA1D8

{id} — bizim EXZ-… və ya sizin order_id (onda həmin nömrəli sonuncu ödəniş qayıdacaq).

Statusu dövrə ilə sorğulamaq nə lazımdır, nə də düzgündür — bunun üçün vebhuklar var. Sorğulamanın məqbul istifadəsi: müştəri saytınıza qayıdanda birdəfəlik yoxlama və «asılı qalmış» sifarişlər üzrə saatda bir tutuşdurma.

Ödənişlərin siyahısı

SiyahıGET /api/v2/payments
GET /api/v2/payments?limit=25&status=succeeded&order_id=1024
ParametrTəsvir
limit1–100, susmaya görə 25
statusYuxarıdakı statuslardan istənilən biri
order_idSizin sifariş nömrəniz
from, toTarixlər YYYY-MM-DD və ya YYYY-MM-DD HH:MM
starting_afterNövbəti səhifənin kursoru
{
  "object": "list",
  "data": [ { "…ödəniş…" } ],
  "has_more": true,
  "next_cursor": "EXZ-20260922-11E5866313",
  "request_id": "req_…"
}

has_more true olduqca next_cursor dəyərini starting_after kimi göndərin.

Geri qaytarma

Geri qaytarmaPOST /api/v2/payments/{id}/refunds
{ "amount": 3000, "reason": "Müştəri maldan imtina etdi" }

amount göstərilmədikdə qalığın tam məbləği qaytarılır.

{
  "object": "refund_result",
  "refund": {
    "id": "ref_12",
    "object": "refund",
    "payment_id": "EXZ-20260922-7FD54BA1D8",
    "amount": 3000,
    "currency": "AZN",
    "status": "succeeded",
    "reason": "Müştəri maldan imtina etdi",
    "created_at": "2026-09-22T19:05:00+04:00"
  },
  "payment": { "…yenilənmiş ödəniş…" },
  "request_id": "req_…"
}

Ödəniş üzrə geri qaytarmaların siyahısı: GET /api/v2/payments/{id}/refunds.

KodNə vaxt
payment_not_payableÖdəniş ödənilməyib — qaytarmağa bir şey yoxdur
refund_amount_invalidMəbləğ qalıqdan çoxdur
refund_failedŞlüz geri qaytarmanı qəbul etmədi

Geri qaytarma da Idempotency-Key dəstəkləyir — mütləq istifadə edin.

Sayt olmadıqda və ya hesab əl ilə verildikdə.

Link yaratmaqPOST /api/v2/links
{ "amount": 12000, "currency": "AZN", "description": "Hesab №77", "max_uses": 3, "expires_at": "2026-10-01" }
SahəTəsvir
amountMinimal vahidlərdə tam ədəd. Göstərməsəniz — məbləği ödəyən daxil edəcək
currencySusmaya görə mağazanın valyutası
description200 simvola qədər
max_usesLink üzrə neçə dəfə ödəmək olar. Göstərməsəniz — məhdudiyyətsiz
expires_atBu andan sonra link işləmir
typedynamic (susmaya görə) və ya static
{
  "id": "idP62kFt",
  "object": "payment_link",
  "url": "https://exezine.az/l/idP62kFt",
  "amount": 12000,
  "currency": "AZN",
  "max_uses": 3,
  "uses": 0,
  "expires_at": null,
  "active": true,
  "created_at": "2026-09-22T19:10:00+04:00"
}

Oxumaq: GET /api/v2/links/{id}.

Vebhuklar — nəticəni öyrənməyin əsas yolu

Ünvan və hadisələr kabinetdə qurulur: «API və vebhuklar».

HadisəNə vaxt
payment.succeededÖdəniş keçdi
payment.failedİmtina
payment.expiredÖdəniş müddəti bitdi
refund.succeededGeri qaytarma keçdi
Hadisənin gövdəsiSizin ünvana POST
{
  "id": "evt_5f0f0c55264b546ecb1a3045",
  "type": "payment.succeeded",
  "api_version": "2026-09-22",
  "livemode": true,
  "created_at": "2026-09-22T18:45:00+04:00",
  "data": { "object": { "…ödəniş…" } }
}

refund.succeeded üçün data daxilində əlavə olaraq refund da olur.

Başlıqlar
X-Exezine-Event: payment.succeeded
X-Exezine-Event-Id: evt_5f0f0c55264b546ecb1a3045
X-Exezine-Attempt: 1
X-Exezine-Signature: t=1790095200,v1=6c1f…a93

İmzanın yoxlanması — mütləqdir

İmza "<t>.<sorğunun xam gövdəsi>" sətrindən HMAC-SHA256 alqoritmi ilə, kabinetdəki vebhuk sirriniz açar kimi istifadə olunmaqla hesablanır.

PHP
<?php
$secret = 'kabinetdən_sizin_sirriniz';
$body   = file_get_contents('php://input');          // XAM gövdə, json_decode-dan əvvəl
$sig    = $_SERVER['HTTP_X_EXEZINE_SIGNATURE'] ?? '';

if (!preg_match('~t=(\d+),v1=([a-f0-9]{64})~', $sig, $m)) {
    http_response_code(400); exit;
}
$expected = hash_hmac('sha256', $m[1] . '.' . $body, $secret);
if (!hash_equals($expected, $m[2]) || abs(time() - (int) $m[1]) > 300) {
    http_response_code(400); exit;                    // imza uyğun gəlmədi və ya gecikib
}

$event = json_decode($body, true);
// … sizin emalınız …
http_response_code(200);
echo 'OK';
Node.js
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  const m = /t=(\d+),v1=([a-f0-9]{64})/.exec(header || '');
  if (!m) return false;
  const expected = crypto.createHmac('sha256', secret)
    .update(`${m[1]}.${rawBody}`).digest('hex');
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
  return ok && Math.abs(Date.now() / 1000 - Number(m[1])) < 300;
}

İmza xam gövdədən hesablanır. Freymvorkunuz JSON-u artıq parçalayıbsa, məhz ilkin sətri götürün: yenidən yığılmış JSON.stringify başqa baytlar verəcək və imza uyğun gəlməyəcək. «İmza yoxlanmır» şikayətinin ən çox rast gəlinən səbəbi budur.

Emal qaydaları

  1. Tez 200 cavab verin. Ağır işi fon növbəsinə salın. 2xx-dən fərqli istənilən cavab uğursuzluq sayılır.
  2. Hadisə iki dəfə gələ bilər. Hadisənin id dəyərini (evt_…) yadda saxlayın və təkrarları nəzərə almayın.
  3. Təkrarlar. Uğursuzluqda 8 dəfə təkrarlayırıq: 1 dəq, 5 dəq, 30 dəq, 2 saat, 6 saat, 12 saat, 24 saat sonra. Sonra hadisə çatdırılmamış kimi işarələnir və kabinetdən düymə ilə yenidən göndərilə bilər.
  4. Ardıcıllığa zəmanət yoxdur. Gəliş sırasına deyil, gövdədəki status dəyərinə əsaslanın.
  5. Pulu vebhuka görə hesablayın, müştərinin sayta qayıtmasına görə yox: müştəri vərəqi bağlaya bilər, ödəniş isə keçər.

Kabinetdə «Test hadisəsi göndər» düyməsi var — real imza ilə test.ping gələcək. Canlı başlanğıcdan əvvəl qəbuledicini yoxlamaq üçün rahatdır.

Müştərinin sayta qaytarılması

Ödənişdən sonra müştərini sizin return_url ünvanınıza yönləndirir və əlavə edirik:

https://shop.az/thanks?payment_id=EXZ-20260922-7FD54BA1D8&status=succeeded

Bu parametrlər yalnız «Təşəkkür» səhifəsini göstərmək üçündür. Sifarişi ödənilmiş saymaq yalnız vebhukdan və ya GET /payments/{id} sorğusundan sonra olar: müştəri ünvan sətrini redaktə edə bilər.

Quraşdırılmış forma

Müştəri sizin səhifənizdə qalır, kart forması isə bizim verdiyimiz çərçivədə açılır. Kart məlumatları sizin saytdan keçmir.

  1. Saytınızın domenlərini bizə bildirin — onları ağ siyahıya salacağıq, əks halda brauzer quraşdırmaya icazə verməyəcək.
  2. Səhifədə:
QoşulmaHTML + JS
<div id="pay"></div>
<script src="https://exezine.az/assets/js/exezine-checkout.js"></script>
<script>
  // checkout_url POST /api/v2/payments cavabında gəlib
  ExezineCheckout.mount('#pay', {
    url: payment.checkout_url,
    onSuccess: function (p) { location.href = '/thanks?id=' + p.payment_id; },
    onFailure: function (p) { alert('Ödəniş keçmədi'); }
  });
</script>

Çərçivə əvəzinə sadə keçid:

ExezineCheckout.redirect(payment.checkout_url);

Bu skriptin verdiyi nəticə interfeys üçündür. Malı göndərmək və sifarişin statusunu dəyişmək — yalnız vebhuka görə.

Qoşulmanın ardıcıllığı

  1. Test açarı alın, GET /api/v2/ping çağırın — əlaqəyə əmin olun.
  2. Vebhuk ünvanını qurun, «Test hadisəsi göndər» düyməsini basın, imzanın uyğun gəlməsinə nail olun.
  3. Yaradılmadan payment.succeeded hadisəsinə qədər test ödənişi keçirin.
  4. Eyni Idempotency-Key ilə təkrar sorğunu yoxlayın.
  5. Geri qaytarmanı yoxlayın — tam və qismən.
  6. Canlı açar alın və 3–5-ci bəndləri kiçik məbləğlə təkrarlayın.
  7. Test açarını canlı serverin tənzimləmələrindən silin.

Nə etmək lazım deyil

  • GET /payments/{id} sorğusunu dövrə ilə göndərmək — bunun üçün vebhuklar var.
  • Müştəri qayıdanda ünvan sətrindəki parametrlərə inanmaq.
  • Açarı brauzerdə və ya mobil tətbiqdə saxlamaq.
  • Idempotency-Key olmadan ödəniş göndərmək — şəbəkə qırılanda dublikat alacaqsınız.
  • Xətanın message mətnini təhlil etmək — code dəyərinə baxın.
ƏlamətSəbəb
İmza heç cür uyğun gəlmirGövdə yoxlamadan əvvəl freymvork tərəfindən parçalanıb. Xam gövdə lazımdır
Məbləğlər 100 dəfə böyükdür4990 əvəzinə 49.90 göndərilib
422 amount_invalidOnluq kəsr göndərilib. API-də yalnız tam ədədlər var
Bir sifarişə iki ödənişIdempotency-Key yoxdur
Mövcud ödənişə 404Canlı mühitdə test açarı və ya əksinə

Dəstək

support@exezine.az ünvanına yazın. Cavabdakı request_id dəyərini və vaxtı dəqiqəyə qədər göstərin — belə olduqda sorğunu jurnalda dərhal tapırıq.

Cavab tapmadınız və ya nəsə yazıldığı kimi işləmir? Bizə yazın — konkret cavab veririk.

support@exezine.az