Ödeme Durumu — POST /plugin-api/payments/status ✓ Canlı

Ödeme terminali SAĞLAYICISININ tahsilat sonucunu ve ilerlemesini platforma bildirdiği kanal. payment.terminal capability'sini sağlayan eklentiler kullanır: komut size gelir, sonucu bu uçtan geri yazarsınız. approved ve reversed durumlarında onaylanan tutar ZORUNLUDUR.

⚠️ BU SAYFADAKİ BULUT KOMUT SÖZLEŞMESİ GÖZDEN GEÇİRİLİYOR — entegrasyonunu buna göre KURMA. Mimari yön değişti: ödeme komutu artık bizim bulutumuzdan sağlayıcının bulutuna gitmiyor; kasa uygulaması sağlayıcının cihazdaki uygulamasına yerel olarak konuşuyor. Yeni sözleşme henüz canlı değil ve ölçülmedi, o yüzden burada yayınlanmadı — hazır olduğunda bu sayfa güncellenecek.

Geçerliliğini KORUYAN kısımlar (bunlara güvenebilirsin): cihaz kaydı (connectors/*) · terminals:read/terminals:write · payment.terminal capability'si · cihaz kimliği (kayıt kodu, cihaz anahtarı, DER imza, oturum token'ı) · pazar ayrımı (TR/EU) ve kalem dökümü · ödeme satırı yazımı (append, lineId, expectedUuid) ve payments:write.

Gözden geçirilen kısım YOL, ANLAM DEĞİL. Değişen: komutun size nasıl ulaştığı, callbackUrls.status, ve sonucu hangi uçtan bildireceğiniz — çünkü bildiren taraf artık sağlayıcının bulutu değil cihazdaki uygulaması, dolayısıyla kimlik de eklenti API anahtarı değil cihaz oturum token'ı olacak.

✅ Yerel akışın İKİ YARISI DA artık yayında ve ölçüldü: cihaz tutarı GET /plugin-api/payments/{paymentId} ile çeker, sonucu POST …/{paymentId}/result ile bildirir — ikisi de cihaz oturum JWT'si ile (ikisi de yalnız dev'de, production'a dağıtılmadı).

Bu sayfadaki uç duruyor ve değişmedi, ama yerel akışta bildiren taraf cihaz olduğu için yeni yolu kullan. Bu sayfa, sağlayıcının BULUTUNDAN bildirim yapan eski model için geçerliliğini koruyor.

Buna karşılık bu sayfadaki SÖZLEŞME ANLAMI korunuyor (platform ekibi teyit etti; karar çekirdeği ikinci kez yazılmayacak): onaylanan tutar bildirilmek zorundadır ve talep edilenden büyük olamaz (onay tavanı), sonuç durumları aynı anlamları taşır, sahiplik / durum makinesi / tekillik (aynı bildirimin tekrarı yazma üretmez) korunur. Yani ne bildireceğinizi şimdiden modelleyebilirsiniz; değişecek olan nereye ve hangi kimlikle göndereceğiniz.

⚠️ ALAN ADINA ve BİRİME kod yazmayın. Aşağıdaki sayfa bugünkü uç için doğrudur (approvedAmountMinor, minor birim tam sayı), ama yeni akışta tel formatı nexo uyumlu olacağı için hem alan adı hem birim değişebilir. Korunan şey tutarın bildirilmesi, o tutarın nasıl adlandırıldığı değil — modelinizi anlam üzerine kurun, alan adı üzerine değil.

ℹ️ Bu ucun kaldırıldığına dair bir bilgi yok; platform ekibi "duruyor ve değişmedi" diyor. ⚠️ Kendin deneyip 401'den varlık sonucu ÇIKARMA: bu yüzeyde tanınmayan bir yol da kimlik katmanına düşüp 401 döner, yani 401 "uç var ama yetkin yok" ile "uç yok"u ayırt etmez.

← API Uçları · Yetenek: Yetenekler (capability) · Ödeme yazma: tables/update-payments.

İstek

POST {RESTOMENUM_BASE}/plugin-api/payments/status
Authorization: Bearer <apiKey>

{
  "paymentId": "pay_01H9...",        // ZORUNLU — komutta gelen ödeme kimliği
  "status": "approved",              // ZORUNLU — sağlayıcı sözlüğü, KÜÇÜK HARF
  "payload": {                       // ops (approved/reversed'da ZORUNLU alan taşır)
    "approvedAmountMinor": 24000     // ZORUNLU (approved · reversed) — onaylanan tutar, minor birim
  }
}
  • Scope: capability:payment.terminal:provide — bu uç yalnız sağlayıcıya açıktır. Auth kurulum API key'i ile.
  • paymentId size komutta gelir; uydurulamaz. Tanımadığı ya da başka bir sağlayıcıya ait bir kimlik → 404.
  • status küçük harf sağlayıcı sözlüğündendir (aşağıdaki tablo).

Durum sözlüğü

AlanTipZorunluAçıklama
sent_to_terminalstring–Komut terminale iletildi.
processingstring–Terminal işliyor (kart okundu, onay bekleniyor).
approvedstring✓payload.approvedAmountMinor ZORUNLU. Tahsilat onaylandı.
declinedstring–Terminal reddetti.
cancelledstring–İşlem iptal edildi (kasiyer ya da müşteri).
reversedstring✓payload.approvedAmountMinor ZORUNLU. Onaylanmış tahsilat geri alındı.
approved ve reversed tutarsız gönderilemez. payload.approvedAmountMinor yoksa istek reddedilir. Sebebi: onaylanan tutar istenenden rutin olarak farklı olur (bahşiş, kısmi onay) ve tutarsız bir onaydan ödeme satırı yazılamaz. Onaylanan tutar istenen tutarı aşarsa 409 amountExceedsRequested.

Yanıt

// kaydedildi
{ "success": true, "data": { "recorded": true, "state": "APPROVED" } }

// BAYAT / SIRASIZ event — HATA DEĞİL, RETRY ETME
{ "success": true, "data": { "recorded": false, "reason": "stale", "state": "APPROVED" } }
⚠️ status gönderirsin, state alırsın — İKİSİ AYRI SÖZLÜKTÜR. Gönderdiğin status küçük harf sağlayıcı sözlüğüdür; dönen state BÜYÜK HARF deneme (attempt) sözlüğüdür ve daha geniştir. Yani status:"approved" gönderir, state:"APPROVED" alırsın.

if (state === 'approved') yazan kod hiçbir zaman eşleşmez ve sessizce yanlış çalışır — fark edilmesi en zor entegrasyon hatası sınıfı. Ayrım kasıtlıdır: biri sağlayıcının bildirdiği olay, diğeri platformun defterindeki durum.
AlanYönSözlük
statussen → platformsent_to_terminal · processing · approved · declined · cancelled · reversed
stateplatform → senDISPATCHING · ACCEPTED · SENT_TO_TERMINAL · PROCESSING · APPROVED · DECLINED · CANCELLED · REVERSED · UNKNOWN · EXPIRED · REJECTED · FAILED · UNRESOLVED
UNRESOLVED da geçerli bir değerdir (belirsizlik çözülemedi) — bu dalı beklemeyi unutma.
recorded: false BİR HATA DEĞİLDİR — RETRY ETME. Bayat ya da sırasız bir durum raporu aldığımızı söyler (ör. approved'dan sonra gelen processing). İstek başarıyla işlendi, yalnız durum ilerletilmedi. Tekrar göndermek durumu değiştirmez, yalnız kota harcar. Nedeni reason alanında gelir (gözlenen değer: "stale").

Bu durumda state işine yarar: olay uygulanmadan önceki, yani hâlâ geçerli olan durumu taşır → denemenin gerçek güncel durumunu buradan öğrenirsin.

Hatalar

HTTPKodAnlamı
403plugin.scope.deniedcapability:payment.terminal:provide onaylı değil.
404—Bilinmeyen paymentId, ya da başka bir sağlayıcıya ait ödeme.
409amountExceedsRequestedOnaylanan tutar istenen tutarı aşıyor.
409conflictingResultAynı ödeme için çelişkili bir sonuç zaten kayıtlı.

Uç adresini KODA GÖMME

⚠️ Bu bölüm yön değişikliğinden ETKİLENİYOR — yeni akışta komut bizim bulutumuzdan gelmiyor, dolayısıyla callbackUrls.status'un karşılığı yeniden tanımlanacak. Aşağıdaki ilke (adresi koda gömme) muhtemelen korunur ama alan adı ve kaynağı değişebilir.
Bu ucun adresini komuttan oku: payload.callbackUrls.status. Komut gövdesi sonuç ucunun URL'ini size taşır. Yolu koda gömersen sandbox kurulumunun sonucunu production'a (ya da tersini) gönderme riskini kendi elinle yaratırsın — ortam köklerini karıştırmak bu entegrasyonda sessiz ve pahalı bir hatadır.

⚠️ Jenerik capability yolu ÇALIŞMAZ — ve bu kasıtlıdır

POST /plugin-api/capabilities/payment.terminal/status 404 döner. Bu bir eksiklik değil, bilinçli bir güvenlik kararıdır: payment.terminal jenerik capability kayıt defterine bilerek eklenmemiştir.

Sebebi: kayıtlı olsaydı jenerik invoke yolu da açılırdı ve o yol, sağlayıcı 25 saniye içinde yanıt vermezse çağrıyı yeniden dener. Mesajlaşmada bu zararsızdır; ödemede karşılığı müşterinin kartından İKİNCİ ÇEKİMDİR. Ödeme bu yüzden kendi özel ucunu kullanır.

Doğru adres yalnızca budur: POST /plugin-api/payments/status (ve adresini komuttaki callbackUrls.status'tan al).

Sözleşme sağlayıcı tipine göre DEĞİŞMEZ

"Sonucu nasıl bildiririm?" sorusunun cevabı tek: yukarıdaki sözleşme. Bulut sağlayıcısı da (bu uca doğrudan HTTP) cihaz yolundan gelen sonuçlar da aynı çekirdeğe girer — tutar tavanı kontrolü, sahiplik doğrulaması, durum makinesi ve kilit bırakma tek kod. Değişen yalnız taşımadır, sözleşme değil.

Pazar — TR ve EU aynı sözleşme DEĞİL

⏳ KAPI BUGÜN ETKİN DEĞİL — beyan et ama korumaya güvenme. Aşağıdaki kural portal tarafında çalışıyor (beyanın doğrulanıyor, kaydediliyor ve platforma aktarılıyor), ama kurulum anında beyan süzülüyor: platform tarafında beyan whitelist'i, payment.terminal'ın bilerek kayıtlı olmadığı runtime kayıt defterine bağlı (o kayıt jenerik invoke yolunu açar ve çift tahsilat üretirdi). Sonuç: pazar kapısı bugün hiç kurulmuyor — beyan etmeyen de reddedilmiyor, yanlış pazar beyan eden de.

Senin için pratik sonucu: markets'i yine de doğru beyan et (portal zaten zorunlu tutuyor ve düzeltme geldiğinde kendiliğinden devreye girer) — ama yanlış pazardaki bir terminale bağlanmayacağını VARSAYMA; bugün o koruma yok, uyumu kendi tarafında da kontrol et. Düzeltme platform tarafında planlı; geldiğinde bu uyarı kalkacak.
Sağlayıcıysan hangi pazara hizmet ettiğini manifest'te BEYAN ETMEK ZORUNDASIN: provides[].markets (["TR"] ya da ["EU"]). Beyan etmezsen hiçbir terminale bağlanamazsın — 424 plugin.payment.providerMarketMismatch. Beyan bilerek kurulum anında zorlanır: uyumsuzluk kart çekilirken değil, bağlanırken ortaya çıksın.

markets değerleri ne anlama gelir

Platform ve portal aynı kuralı uygular; ikisi de fail-closed'dır — bozuk ya da boş bir beyan kapıyı açmaz.

BeyanSonuç
["TR"]Yalnız TR terminallerine bağlanır; EU terminali reddedilir.
alan hiç yok"Beyan yok" — kapı uygulanmaz (geri-uyum). ⚠️ Ama yeni bir sağlayıcı bu duruma düşemez: payment.terminal beyan edip markets vermezsen submit'te issue alırsın.
[]"Hiçbir pazar" — "beyan yok" ile AYNI ŞEY DEĞİL. Her terminal reddedilir. Boş bırakıp "kapı kapansın" sanma; bu, hiçbir yere bağlanmamak demektir.
"TR"Dizi değil → reddedilir ve [] gibi ("hiçbir pazar") işlenir. Sessizce yok sayılmaz — bozuk bir beyanın kapıyı açması beyanın tam tersi olurdu.
TR — TürkiyeEU — Yurt dışı
Mali kalem dökümüZORUNLU — cihaz kalemsiz komutu kabul etmezyok
VKN / fatura noopsiyonel — müşteri kaydından türetilir, kasiyer elle girmez, sen göndermezsinyok
Ödeme modeliartımlı — aynı fişe parça parça ödeme eklenirtek seferlik, tam tutar
  • Mali kalem dökümünü EKLENTİ sağlar, platform üretmez. İhtiyacın olan her şey okuma uçlarında zaten var: packets/get satır başına vatRate · productId · quantity · lineTotalDecimal · options döner.
  • Bir restoran TEK pazarda çalışır. Pazar terminal kurulurken bir kez seçilir ve sonradan DEĞİŞTİRİLEMEZ (409 plugin.payment.marketImmutable) — değişse aynı terminalin geçmiş fişleri bir kurala, yenileri başkasına tabi olurdu.
  • Terminal kaydında pazar yazılmamışsa ödeme hiç başlamaz: 400 plugin.payment.terminalMarketUnset. Varsayılan yoktur — TR varsaymak, eksik kurulmuş bir terminali sessizce Türkiye mali kurallarına sokardı.

Cihaz agent'ı yazanlar için — kimlik tazeleme ve imza

Bu bölüm YALNIZ cihaz agent'ı yazanları ilgilendirir. Bulut PSP eklentisi yazıyorsan (sonucu HTTP ile bu sayfadaki uçtan bildiriyorsan) buraya ihtiyacın yok — bunlar taşıma katmanının sözleşmesidir, plugin-api yüzeyi değil.

Cihaz kimliği ve oturum token'ı

Kimlik zinciri kayıt kodu → cihaz anahtarı → imzalı oturum token'ı şeklindedir ve yön değişikliğinden etkilenmedi: sonucu bildiren tarafın kim olduğunu doğrulamanın tek yolu bu olduğu için korunuyor.

  • Oturum token'ı YALNIZ POST /v1/connectors/session'dan alınır. Bu uç cihazın durumunu kontrol eder → devre dışı bırakılmış bir cihaz token alamaz. Devre dışı bırakma güvencesinin dayandığı nokta budur; bir dönem kapatma kodu tanımlı olduğu hâlde hiç üretilmediği için devre dışı cihazlar çalışmaya devam ediyordu, bu uç o açığı kapatıyor.
  • ⚠️ Token'ın soket ömrüyle ilişkisi (yenileme penceresi, kapanış kodları) burada YAYINLANMIYOR — o kısım bulut taşıma katmanına aitti ve yön değişikliğiyle konu dışı kaldı. Yerel akışın kimlik yenileme sözleşmesi ölçüldüğünde eklenecek.
⚠️ Soket/bağlantı yönetimi bölümü KALDIRILDI. Bağlantı ömrü, yeniden bağlanma zamanlaması ve kapanış kodları bulut taşıma katmanına aitti; o katman yön değişikliğiyle birlikte konu dışı kaldı. Yerel akışın bağlantı sözleşmesi hazır olduğunda burada yayınlanacak — o zamana kadar bu konuda bir kural yok.

Aşağıdaki imza ve kimlik bölümü GEÇERLİLİĞİNİ KORUYOR: cihaz kimliği (kayıt kodu → cihaz anahtarı → imzalı oturum token'ı) yön değişikliğinden etkilenmedi; sonucu bildiren tarafın kim olduğunu doğrulamanın tek yolu olduğu için korunuyor.

İmza biçimi — DER, P1363 DEĞİL

// Cihaz session imzası — EC P-256 / SHA-256 / DER (ASN.1 SEQUENCE{r,s})
// ✅ ölçüldü: DER 71 bayt → doğrulanır · P1363 ham r‖s 64 bayt → REDDEDİLİR

// Node — varsayılan zaten DER, ek iş yok
const sig = crypto.sign('sha256', payload, privateKey);          // DER ✅

// Java / Android — Signature zaten DER üretir
Signature.getInstance("SHA256withECDSA");                          // DER ✅

// .NET — ⚠️ VARSAYILAN P1363'TÜR, ÇEVİRMEN GEREKİR
ecdsa.SignData(payload, HashAlgorithmName.SHA256);                 // P1363 ❌
ecdsa.SignData(payload, HashAlgorithmName.SHA256,
               DSASignatureFormat.Rfc3279DerSequence);             // DER ✅
Yanlış imza biçimi sessiz kalmaz — HER isteği reddeder. Ama sebep unauthorized göründüğü için insanlar "anahtar mı bozuk, parmak izi mi tutmuyor" diye yanlış yerde arar ve gün kaybeder. Reddediliyorsan önce imza biçimini ölç: DER ~70–72 bayt ve 0x30 ile başlar; P1363 tam 64 bayt'tır.

Açık anahtar biçimi: SPKI PEM (-----BEGIN PUBLIC KEY-----). Ham blob ya da base64 DER kayıt aşamasında reddedilir.

Hız sınırı

Kova cap:payment.terminal, 600/dk. Cömert olmasının sebebi tek bir tahsilatın 5–15 ilerleme event'i üretmesi: düşen bir durum raporu ödemeyi UNKNOWN durumda bırakır, o yüzden ilerleme bildirimlerini kısmak yerine sınır yükseltildi. Yine de recorded:false aldığında tekrar gönderme.

İlgili: Yetenekler · tables/update-payments (expectedUuid oturum kapısı) · payments:write.