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.
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.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.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ı).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.401 "uç var ama yetkin yok" ile "uç yok"u ayırt etmez.← API Uçları · Yetenek: Yetenekler (capability) · Ödeme yazma: tables/update-payments.
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
}
}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).| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| sent_to_terminal | string | – | Komut terminale iletildi. |
| processing | string | – | Terminal işliyor (kart okundu, onay bekleniyor). |
| approved | string | ✓ | payload.approvedAmountMinor ZORUNLU. Tahsilat onaylandı. |
| declined | string | – | Terminal reddetti. |
| cancelled | string | – | İşlem iptal edildi (kasiyer ya da müşteri). |
| reversed | string | ✓ | 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.// 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.| Alan | Yön | Sözlük |
|---|---|---|
| status | sen → platform | sent_to_terminal · processing · approved · declined · cancelled · reversed |
| state | platform → sen | DISPATCHING · 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").state işine yarar: olay uygulanmadan önceki, yani hâlâ geçerli olan durumu taşır → denemenin gerçek güncel durumunu buradan öğrenirsin.| HTTP | Kod | Anlamı |
|---|---|---|
| 403 | plugin.scope.denied | capability:payment.terminal:provide onaylı değil. |
| 404 | — | Bilinmeyen paymentId, ya da başka bir sağlayıcıya ait ödeme. |
| 409 | amountExceedsRequested | Onaylanan tutar istenen tutarı aşıyor. |
| 409 | conflictingResult | Aynı ödeme için çelişkili bir sonuç zaten kayıtlı. |
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.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.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.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.POST /plugin-api/payments/status (ve adresini komuttaki callbackUrls.status'tan al)."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.
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.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.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 gelirPlatform ve portal aynı kuralı uygular; ikisi de fail-closed'dır — bozuk ya da boş bir beyan kapıyı açmaz.
| Beyan | Sonuç |
|---|---|
| ["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ürkiye | EU — Yurt dışı | |
|---|---|---|
| Mali kalem dökümü | ZORUNLU — cihaz kalemsiz komutu kabul etmez | yok |
| VKN / fatura no | opsiyonel — müşteri kaydından türetilir, kasiyer elle girmez, sen göndermezsin | yok |
| Ödeme modeli | artımlı — aynı fişe parça parça ödeme eklenir | tek seferlik, tam tutar |
vatRate · productId · quantity · lineTotalDecimal · options döner.409 plugin.payment.marketImmutable) — değişse aynı terminalin geçmiş fişleri bir kurala, yenileri başkasına tabi olurdu.400 plugin.payment.terminalMarketUnset. Varsayılan yoktur — TR varsaymak, eksik kurulmuş bir terminali sessizce Türkiye mali kurallarına sokardı.plugin-api yüzeyi değil.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.
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.// 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 ✅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.-----BEGIN PUBLIC KEY-----). Ham blob ya da base64 DER kayıt aşamasında reddedilir.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.