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 və ləğv, saxlanmış kartlar və avtomatik silinmələr, quraşdırılmış forma.

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

03.10.2026 yenilikləri: appearance sahəsi ilə ödəniş formasının tərtibatı, quraşdırılmış forma dərhal açılır və çərçivənin hündürlüyünü özü seçir (skript ?v=2), «Kartı yadda saxla» qeydi — yalnız sizin istəyinizlə. Ləğv edilmiş və ya müddəti bitmiş ödənişi artıq açıq bank səhifəsində ödəmək olmur (ətraflı); vebhukların emal qaydaları dəqiqləşdirilib.

01.10.2026 yenilikləri: saxlanmış kartlar və avtomatik silinmələr, ödənişin ləğvi, öz ödəniş müddətiniz və tez gələn payment.expired, order_id üzrə ikiqat ödənişdən qorunma, sınaq vebhukları üçün ayrıca ünvan. Bütün dəyişikliklər əlavədir: mövcud inteqrasiya düzəlişsiz işləyir.

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.idxeyrSizin sisteminizdə alıcının id-si (64 simvola qədər: latın hərfləri, rəqəmlər, _ - . : @). Saxlanmış kartlar ona bağlanır
expires_in_minutesxeyrÖz ödəniş müddətiniz: 5–1440 dəqiqə. Verilməsə — mağazanızın müddəti (standart olaraq 24 saat)
save_payment_methodxeyrtrue — kartı növbəti silinmələr üçün saxlamaq, "optional" — alıcıdan qeyd ilə soruşmaq. Bax: «Saxlanmış kartlar»
payment_methodxeyrpm_… — ödəniş səhifəsi olmadan saxlanmış kartdan silmək
allow_duplicate_orderxeyrtrue — artıq ödənilmiş order_id üzrə ikinci ödənişə icazə vermək
appearancexeyrKart formasının tərtibatı: rənglər, şriftlər, başlığı və altlığı gizlətmək. Bax: «Ödəniş formasının tərtibatı»

customer haqqında. Emitent banklar 3-D Secure 2-də riski məhz bu məlumatlara görə qiymətləndirir. Heç olmasa email, ip və country 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.

İkiqat ödənişdən qorunma. Bu order_id üzrə artıq ödənilmiş ödəniş varsa, ikincisi yaradılmır: 409 order_already_paid qayıdır, error.payment_id sahəsində isə həmin ödənişin id-si olur. Beləliklə «Ödə» düyməsinin təkrar basılması və ya sizin tərəfdə tapşırığın təkrarı pulu iki dəfə silməyəcək. İkinci ödəniş qəsdən lazımdırsa (məsələn, əlavə ödəniş), "allow_duplicate_order": true göndərin. Alıcı sifarişi yenə də iki dəfə ödəyibsə (ilk ödənişdən əvvəl iki ödəniş səhifəsi açıb), ikinci ödənişdə duplicate_of sahəsi doldurulacaq, biz isə Telegram-a bildiriş göndərəcəyik.

Ödəniş müddəti

Hər ödənişin müddəti var — expires_at sahəsi. Onu expires_in_minutes təyin edir, göndərilməyibsə — mağazanızın tənzimləməsi (standart olaraq 24 saat). Müddət bitəndən sonra ödəniş səhifəsi kartı qəbul etmir və bir neçə dəqiqə ərzində payment.expired vebhuku gəlir. Bank son anda hələ 3-D Secure aparırsa, müddətdən sonra 15 dəqiqəyə qədər gözləyirik və yalnız bundan sonra ödənişi bağlayırıq.

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": { "id": "user-77", "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",
  "next_action": null,
  "initiator": "customer",
  "save_payment_method": false,
  "payment_method": null,
  "duplicate_of": null,
  "expires_at": "2026-09-23T18:30:00+04:00",
  "paid_at": null,
  "canceled_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
customer.idGöndərmisinizsə, alıcının id-si
expires_atÖdənişi hansı ana qədər ödəmək olar
initiatorcustomer — alıcı ödəyir, merchant — alıcısız saxlanmış kartdan silinmə
save_payment_methodNə istənilib: true, "optional" və ya false
payment_methodSaxlanmış kart (pm_…): bu ödənişin nəticəsində saxlanılan və ya silinmənin aparıldığı kart. Əks halda null
next_actionrequires_action zamanı: {"type":"redirect","url":"…"} — alıcını hara göndərmək lazımdır
duplicate_ofSifariş iki dəfə ödənilibsə, eyni order_id üzrə ilk ödənişin id-si
canceled_atÖdənişi nə vaxt ləğv etmisiniz

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
canceledSizin tərəfinizdən ləğv edilib (POST /payments/{id}/cancel)
requires_actionSaxlanmış kartdan silinmə alıcının təsdiqini gözləyir (3-D Secure), bax next_action

Yekun statuslar: succeeded, failed, expired, canceled, 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
customer_idBir alıcının ödənişləri (sizin customer.id)
{
  "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.

Ödənişin ləğvi

Alıcı fikrini dəyişibsə və ya sifariş artıq aktual deyilsə, ödənilməmiş ödənişi müddəti gözləmədən özünüz bağlayın.

LəğvPOST /api/v2/payments/{id}/cancel
curl -X POST -H "Authorization: Bearer $KEY" -H "Idempotency-Key: cancel-1024" \
  https://exezine.az/api/v2/payments/EXZ-20260922-7FD54BA1D8/cancel
  • Uğur — 200 və canceled statusunda ödəniş, üstəlik payment.canceled vebhuku.
  • Yalnız pending və ya requires_action statusundakı ödənişi ləğv etmək olar. Əks halda — 409 payment_not_cancelable, error.payment_status sahəsində isə onun cari statusu (məsələn, succeeded: alıcı ödəməyə çatıb).
  • Ləğvdən əvvəl bankdan bir daha soruşuruq: ödənilmiş ödənişi ləğv etmək olmaz.
  • Ləğv edilmiş ödənişin checkout_url linki artıq ödənişə aparmır.

Bank səhifəsi artıq açıqdırsa. Alıcı ödəniş səhifəsini ləğvdən əvvəl aça bilərdi. Adətən onun üzərindən ödənişi keçirmək artıq mümkün olmayacaq: silinmədən əvvəl ödəniş şlüzü bizdən soruşur və ləğv edilmiş ödənişi rədd edirik — alıcı «Ödəniş mağaza tərəfindən ləğv edilib» görəcək. Lakin bunu 100% zəmanət etmək olmur (məsələn, şlüzlə əlaqə xətası zamanı). Ona görə ləğvdən sonra gələn payment.succeeded hadisəsini adi ödəniş kimi emal edin (və ya geri qaytarma edin).

Saxlanmış kartlar və avtomatik silinmələr

Abunəliklər, avtomatik ödənişlər və «bir kliklə» ödəniş üçün: alıcı kartı bir dəfə bizim səhifədə daxil edir, sonra isə ödəniş səhifəsi olmadan ondan özünüz silirsiniz. Kart nömrəsini nə siz, nə də biz görürük — sizdə yalnız pm_… identifikatoru və kartın maskası var.

Funksiya sorğu ilə aktivləşdirilir: dəstəyə yazın, biz onu bankla razılaşdıracağıq. Aktiv olub-olmadığı GET /ping cavabında (features.saved_cards) və kabinetdə görünür.

1. Ödəniş zamanı kartı saxlamaq

İlk ödənişPOST /api/v2/payments
{
  "amount": 4990,
  "order_id": "sub-77-1",
  "save_payment_method": true,
  "customer": { "id": "user-77", "email": "buyer@example.com" },
  "return_url": "https://shop.az/thanks"
}
DəyərNecə işləyir
trueKart həmişə saxlanılır. Alıcının gələcək silinmələrə razılığını siz öz tərəfinizdə alırsınız (abunəlik zamanı qeyd, ofertanın şərtləri)
"optional"Ödəniş səhifəsində alıcı «Kartı növbəti ödənişlər üçün yadda saxla» qeydini görür. Qeyd qoymayıbsa — kart saxlanılmayacaq

Alıcı kartı daxil edir və adi qaydada 3-D Secure-dan keçir. Ödəniş succeeded olanda onda payment_method görünəcək:

"payment_method": {
  "id": "pm_3f9a1c7e5b2d4f6a8c0e1b3d",
  "object": "payment_method",
  "type": "card",
  "livemode": true,
  "customer_id": "user-77",
  "card": { "brand": "visa", "last4": "4242", "exp_month": 7, "exp_year": 2030, "country": "AZ" },
  "status": "active",
  "source": "checkout",
  "metadata": {},
  "created_at": "2026-10-01T12:00:00+04:00",
  "last_used_at": null
}

Eyni obyekt ayrıca payment_method.saved vebhuku ilə gələcək. pm_…-ni alıcının yanında saxlayın. Alıcı yenidən eyni kartla ödəyərsə, yeni kart yaranmayacaq — əvvəlki yenilənəcək.

2. Saxlanmış kartdan silmək

Avtomatik silinməPOST /api/v2/payments
{
  "amount": 4990,
  "order_id": "sub-77-2026-11",
  "description": "Abunəlik, noyabr",
  "payment_method": "pm_3f9a1c7e5b2d4f6a8c0e1b3d",
  "customer": { "id": "user-77" }
}

Ödəniş səhifəsi yoxdur: bank dərhal cavab verir və 201 cavabı nəticəni artıq status-da ehtiva edir:

statusNə etməli
succeededPul silinib. payment.succeeded vebhuku da gələcək
failedBank imtina edib, səbəb failure-dadır. payment.failed gələcək
requires_actionBank alıcının təsdiqini tələb edir (3-D Secure). Alıcıya next_action.url linkini göndərin; nəticə vebhukla gələcək
pendingBank hələ cavab verməyib. Nəticə vebhukla gələcək; silinməni yeni idempotentlik açarı ilə təkrarlamayın
SahəTəsvir
payment_methodSilinmə üçün mütləqdir: pm_…
initiatormerchant (standart) — alıcısız silinmə (abunəlik, avtomatik ödəniş); customer — alıcı hazırda sizdədir və saxlanmış kartla «Ödə» düyməsini özü basıb
mit_typeYalnız bizimlə razılaşdırılaraq: alıcısız silinmənin səbəbi — delayed_charge, increment, resubmission, reauthorization, no_show
customer.idGöndərilibsə, kartın alıcısı ilə üst-üstə düşməlidir — əks halda 422 customer_mismatch

Mütləq Idempotency-Key göndərin — məsələn sub-77-2026-11. Şəbəkə qırılandan sonra sorğunun təkrarı eyni ödənişi qaytaracaq, pulu ikinci dəfə silməyəcək. Bank imtina etdikdə (failed) isə yeni cəhdi yeni açarla və gündə bir dəfədən tez olmayaraq edin: imtina edilmiş kart üzrə tez-tez təkrarları banklar fırıldaqçılıq kimi qəbul edir.

3. Kartların idarə edilməsi

SorğuNə edir
GET /api/v2/payment_methods?customer_id=user-77Alıcının kartları (customer_id olmadan — hamısı). Parametrlər: limit, starting_after, include_revoked=1
GET /api/v2/payment_methods/{pm_id}Bir kart
DELETE /api/v2/payment_methods/{pm_id}Kartı silmək (alıcı onu sizdə ayırıb). Onunla artıq ödəmək olmaz

Kartın statusu: active, expired (kartın müddəti bitib) və ya revoked (silinib). Sınaq və real kartlar ödənişlər kimi ayrılıb.

Əvvəl saxlanmış kartların köçürülməsi

Əvvəllər ekvayer bankla birbaşa işləmisinizsə və sizdə eyni mağazanın alıcılarının kart tokenləri artıq varsa, onları köçürmək olar — alıcılar kartları yenidən daxil etməli olmayacaq. Köçürməni sorğu ilə aktivləşdiririk.

KöçürməPOST /api/v2/payment_methods
{
  "customer_id": "user-77",
  "token": "bankdan əvvəllər alınmış kart tokeni",
  "card": { "brand": "visa", "last4": "4242", "exp_month": 7, "exp_year": 2030 },
  "metadata": { "migrated": "2026-10" }
}

Cavab — 201 və kart obyekti (pm_…). Eyni tokenin eyni alıcıya təkrar köçürülməsi həmin kartı qaytaracaq (200). Tokeni əvvəlcədən yoxlamaq olmur: etibarsızdırsa, ilk silinmə imtina qaytaracaq.

Saxlanmış kart xətaları

KodNə vaxt
saved_cards_disabledFunksiya mağazanız üçün aktiv deyil
payment_method_invalidKart tapılmadı (yazı xətası, açarın başqa rejimi)
payment_method_revokedKart silinib
payment_method_expiredKartın müddəti bitib — alıcıdan yeni kartla saxlamaqla ödəməsini xahiş edin
payment_method_unusableKartdan istifadə etmək olmur — alıcıdan kartı saxlamaqla yenidən ödəməsini xahiş edin
customer_mismatchKart başqa alıcıya məxsusdur
parameters_conflictBir sorğuda həm payment_method, həm də save_payment_method
save_requires_cardYalnız kartı saxlamaq olar, SBP-ni yox

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
payment.canceledSiz ödənişi ləğv etmisiniz
payment_method.savedAlıcının kartı saxlanılıb (data.object-də — kart, data.payment_id-də — ödəniş)
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

Sınaq hadisələri üçün ayrıca ünvan

Kabinetdə ikinci ünvanı — «Sınaq hadisələri üçün URL» göstərmək olar. Onda sınaq açarı ilə ödənişlər üzrə hadisələr ("livemode": false) ora, real hadisələr isə əsas ünvana gedir. Sınaq ünvanının öz imza sirri var: sınaq serverinin real sirri bilməsinə ehtiyac yoxdur. Göstərilməyibsə — bütün hadisələr əvvəlki kimi əsas ünvana gedir; onları livemode sahəsinə görə fərqləndirin.

İ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 — amma yalnız hadisə saxlanılandan sonra. Cavabı 15 saniyə gözləyirik; ağır işi fon növbəsinə salın. 2xx-dən fərqli istənilən cavab uğursuzluq sayılır və çatdırılmanı təkrarlayırıq. Əvvəlcədən 200 cavab verməyin: emal sonra uğursuz olsa, təkrar artıq olmayacaq.
  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. «Emal edildi» qeydini emalın özü ilə eyni baza tranzaksiyasında (və ya ondan sonra) qoyun, daha tez yox: əks halda ortada xəta olarsa, bizim təkrar dublikat kimi atılacaq.
  3. Təkrarlar. Cəmi 8-ə qədər çatdırılma cəhdi: birincisi dərhal, sonra 1 dəq, 5 dəq, 30 dəq, 2 saat, 6 saat, 12 saat və 24 saat sonra təkrarlar. 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?v=2"></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ə.

Çərçivə necə işləyir

Kart forması aralıq ekran olmadan dərhal açılır. Alıcı onu çarpazla bağlasa, çərçivədə «Kartla ödə» düyməsi görünəcək — forma yenidən açılacaq. Forma uğurlu ödənişdən sonra bağlanıbsa, çərçivə bankın təsdiqinə qədər «Ödəniş yoxlanılır…» göstərir — bu anda ikinci dəfə ödəmək mümkün deyil.

Çərçivənin hündürlüyünü skript özü seçir: kart forması açıq olanda — formHeight (defolt 640 px), ödənişdən sonra — nəticə mesajına görə.

mount parametriTəsvir
urlMütləqdir. API cavabındakı checkout_url
autoOpenfalse — əvvəlcə məbləğ və düymə ilə bizim ekran (əvvəlki kimi). Defolt true
formHeightKart forması açıq olanda çərçivənin hündürlüyü, px (360–1400). Defolt 640
heightÇərçivənin sabit hündürlüyü, px. Onda avto-hündürlük söndürülür
onSuccess, onFailureÖdənişin nəticəsi (interfeys üçün)
onReady, onResize, onMessageÇərçivə yükləndi / hündürlüyü dəyişdi / bizim istənilən mesaj

Skripti yuxarıdakı nümunədəki kimi ?v=2 ilə qoşun: brauzerlər faylların köhnə nüsxələrini uzun müddət saxlayır, yeni ünvan isə yeni versiyaya zəmanət verir.

Bizim skript olmadan öz çərçivəniz. <iframe src="{checkout_url}?embed=1">. Ünvan parametrləri: auto=0 — formanı dərhal açmamaq, fh=640 — formanın hündürlüyü. Hündürlüyü message hadisəsinə görə dəyişin: əgər event.origin === "https://exezine.az", data.source === "exezine" və data.type === "resize"-dirsə, data.height-də lazımi hündürlük pikseldədir.

Ödəniş formasının tərtibatı

Kart formasını saytınızın üslubunda tərtib etmək olar: rənglər, şriftlər, yuvarlaqlaşdırma, başlığı və altlığı gizlətmək. Tərtibat hər ödənişdə appearance sahəsi ilə ötürülür — kabinetdə heç nə qurmaq lazım deyil, onu istənilən an öz kodunuzda dəyişmək olar. Həm quraşdırılmış formada, həm də ayrıca ödəniş səhifəsində işləyir.

Saytın üslubunda tünd formaPOST /api/v2/payments
{
  "amount": 3500,
  "order_id": "fee-77-2026-11",
  "appearance": {
    "widget":  { "backgroundColor": "#0E1114", "buttonsColor": "#C6FF00", "backgroundType": 0 },
    "header":  { "display": "none" },
    "price":   { "color": "#C6FF00", "fontSize": "20px", "fontWeight": "700" },
    "button":  { "pay": { "backgroundColor": "#C6FF00", "color": "#0E1114", "borderRadius": "12px" } },
    "inputs":  { "backgroundColor": "#1A2027", "color": "#F4F6F8", "cvc": { "type": "password" } },
    "footer":  { "display": "none" }
  }
}
BölməNəyi tərtib edir
widgetBütün forma: backgroundColor — fon, buttonsColor — düymələrin rəngi, backgroundType — fon naxışı (0 — naxışsız), şrift
headerBaşlıq (mağazanın adı, «təhlükəsiz ödəniş»). "display": "none" — gizlətmək
price, descriptionMəbləğ və ödənişin təyinatı
buttonDüymələr: card — «Kartla ödə», brands — Apple Pay / Google Pay, pay — «Ödə»
inputsDaxiletmə sahələri; inputs.cvc.type: "password" — CVC-ni nöqtələrlə gizlətmək
cardFaceKartın təsviri
footerLoqotipli altlıq. "display": "none" — gizlətmək

Xassə adları CSS-dəki kimidir, amma bitişik: backgroundColor, fontSize, fontFamily, fontWeight, borderRadius, letterSpacing, margin. Dəyərlər — sətirlər ("#0E1114", "14px"), rəqəmlər, true/false.

Məhdudiyyətlər. İç-içəlik — 4 səviyyəyə qədər, obyektin ölçüsü — 64 KB-a qədər. Sətirlərdə < > { } simvolları, həmçinin javascript:, expression( və @import olmaz. base64 şəkillər buraya sığmır — tərtibat rənglər və şriftlərlə verilir. Xəta olduqda 422 appearance_invalid qayıdacaq, error.param-da isə dəqiq yer, məsələn appearance.header.display.

Biz formatı yoxlayırıq, xassələrin mənasını yox: xassənin adında hərf səhvi xəta yaratmayacaq — xassə sadəcə işləməyəcək. Hamı üçün açmazdan əvvəl görünüşü bir ödənişdə yoxlayın. Ödəniş tərtibatı dəstəkləməyən provayder vasitəsilə keçirilərsə, ödəniş adi qaydada keçəcək, sadəcə forma standart olacaq.

Formada «Kartı yadda saxla» qeydi yalnız save_payment_method: "optional" olduqda görünür. API vasitəsilə digər ödənişlərdə o yoxdur.

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.
  8. Saxlanmış kartlardan istifadə edirsinizsə — save_payment_method ilə ilk ödənişi, pm_… üzrə silinməni, imtinanı və kartın silinməsini yoxlayın.

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ə
409 order_already_paidBu order_id ilə sifariş artıq ödənilib. Bu, ikiqat ödənişdən qorunmadır
Kartdan silinmə həmişə failedKart bloklanıb və ya boşdur. Gündə bir dəfədən tez təkrarlamayın, 3–4 imtinadan sonra alıcıdan yenidən ödəməsini xahiş edin

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