POST /plugin-api/packets/update-payments ✓ CanlıPaketin ödeme satırlarını yazar. İki mod: mode:'replace' (VARSAYILAN — TÜM ödemeleri değiştirir, FULL REPLACE) ve mode:'append' (mevcut satırlara dokunmadan ekler; lineId zorunlu — 🧪 YALNIZ sandbox; production'da yok ve planlanmıyor). paid yeniden hesaplanır (yalnız price>0 satırlar). Transaction'lı. Hiçbir uç paketi KAPATMAZ; paid==total olsa bile finansal kapanış işletme akışındadır.
← API Uçları · Yazma ailesi: create · update · update-orders.
POST {RESTOMENUM_BASE}/plugin-api/packets/update-payments
Authorization: Bearer <apiKey>
{
"packetId": "a72d70cd-...", // ZORUNLU
"mode": "replace", // ops — "replace" (VARSAYILAN) | "append" 🧪 append yalnız SANDBOX (prod'da YOK, planlanmıyor)
"payments": [ // ZORUNLU — replace: paketin YENİ ödeme listesi (FULL REPLACE)
{ "price": 240, "id": "cash", "title": "Nakit", "isDiscount": false,
"lineId": "pay-7c31", // ops (replace) / ZORUNLU (append) — ödeme SATIRININ kimliği (id = YÖNTEM id'si!)
"metadata": [ { "key": "terminal", "value": "kiosk-1" } ] } // ops — DİZİ (map değil)
]
}orders:write.mode opsiyonel, varsayılan "replace". Göndermezsen davranış bit-bit eskisiyle aynıdır — bu bir ekleme, kırıcı değişiklik değil."replace": payments paketin tüm ödemelerini değiştirir. Satır: { price, id, title, isDiscount?, lineId?, metadata? }."append": gönderilen satırlar mevcutların üzerine eklenir — aşağıdaki bölüm.payments şema tavanı) — her iki modda da. replace dokümanın satırlarını gönderdiğinle değiştirdiği için bu, bir replace çağrısından sonra dokümanı fiilen 20 satıra indirir: doküman tavanı değil, istek tavanının yan etkisidir.paid yeniden hesaplanır — yalnız price > 0 satırlar saklanır.payments: [] → paid: 0 (ödemeleri sıfırlar). Yalnız replace: append'de geçerli satır yoksa istek reddedilir.Ödeme satırları tenant'ın gerçek ödeme yöntemlerine karşı doğrulanır ("ID al, sunucuda türet"). Önce payment-methods/list çağır (payment_methods:read), geçerli id'leri oradan al.
| Satır | Kural |
|---|---|
Normal (isDiscount yok/false) | id tenant'ın tanımlı bir yöntemi OLMALI. title yok sayılır (yöntem kaydından türetilir; göndermek hata değil). Yöntemin cash/noreport alanları kayda işlenir. |
İndirim (isDiscount:true) | Doğrulamadan muaf — markalı platform-indirimi (serbest id, ör. "getir-ind"). title bu satırlarda zorunlu; raporda doğru sınıflanır. |
unknown_payment_method alır.mode: "append"mode alanını hiç gönderme. Ekleme modu sandbox'a deploy edildi ve gerçek HTTP çağrılarıyla uçtan uca doğrulandı (18/18 senaryo: ekleme, tekrar gönderim, tutar/yöntem uyuşmazlığı, lineId eksikliği, geriye uyumluluk; ayrıca sandbox.plugins.restomenum.app hostname'i üzerinden 9/9). Bu sayfadaki yanıt gövdeleri o testlerden yakalanmış gerçek gövdelerdir. Production'da HENÜZ YOK — orada mode göndermek isteği komple 400'e düşürür ({ success:false, message: "\"mode\" is not allowed" }; şema bilinmeyen anahtarı reddeder, eski davranışa düşmez). Durum değişirse değişiklik günlüğünden duyurulur.Neden var: POS terminali eklentileri ödeme satırını bu uçtan yazıyor ve full-replace onlar için bir tuzak: "kendi satırımı gönderdim" diyen en doğal geliştirici davranışı, aynı hesaptaki nakit tahsilatı, ikinci kartı ve indirim satırlarını SİLER ve paid düşer. Ekleme modu bunu kaldırır.
POST {RESTOMENUM_BASE}/plugin-api/packets/update-payments
Authorization: Bearer <apiKey>
{
"packetId": "a72d70cd-...",
"mode": "append", // mevcut satırlara DOKUNMAZ — üzerine ekler
"payments": [
{ "price": 240, "id": "m-card", "lineId": "pay-term-01H9..." } // lineId ZORUNLU (tekrar gönderim kalkanı)
]
}append_partial_duplicate).lineId ZORUNLUDUR — keyfi bir kural değil, tekrar gönderim kalkanı: platform kimlik göndermeyen satıra her çağrıda FARKLI bir kimlik üretir, yani ağ hatasında tekrarlanan istek ikinci bir satır olarak eklenir = çift tahsilat. Kimliği kendi kaydından türet ve retry'de aynısını gönder.lineId GÜÇLÜ olmalı: en az 8 karakter ve saf rakam DEĞİL. "00000001" sekiz karakterdir ama ayırt etmez — sayaç kalıbı reddedilir. Eski satırlara tanınan grandfather affı ekleme modunda İŞLEMEZ: af yalnız dokümanda zaten var olan kimlikleri bağışlar, ekleme modundaki yeni satırın kimliği ise tanım gereği dokümanda yoktur (olsaydı tekrar gönderim olurdu). Önerilen kalıp: sabit önek + uuid — pay-01H9XYZ…. (replace'te lineId opsiyoneldir; göndermezsen platform üretir.)paid birleşim üzerinden yeniden hesaplanır; paid ≤ total kapısı aynen geçerli.price ≤ 0 satırlar süzülür → süzme sonrası geçerli satır kalmazsa istek reddedilir.too_many_payment_lines. İkincisi iş kuralı değil, kaçak büyümeye karşı emniyet supabıdır.paid == total olsa bile hesap kapanmaz.replace çağırırsan satırlar gönderdiğinle değişir — 20'nin üstündeki her şey gider. Full-replace tuzağının somut hâli budur ve append tam olarak bunun için var.replayedYanıttaki replayed: true = aynı lineId ile tekrar gönderim algılandı, yazma YAPILMADI, doküman değişmedi. Retry sonrası bunu görüp "ödemem zaten kayıtlı" diyebilirsin. Bu durumda hiçbir event yayılmaz (doküman değişmediği için).
| Kod (400) | Anlamı |
|---|---|
| append_requires_line_id | Ekleme modunda lineId göndermeyen satır var. |
| append_requires_payment_line | İstekte geçerli satır yok (price ≤ 0 satırlar süzüldükten sonra boş). |
| append_partial_duplicate | Satırların bir kısmı zaten yazılı, bir kısmı yeni. Sessizce yalnız yenileri eklemek, çağıranın hangi satırının geçtiğini bilmediği bir durumda çift tahsilat üretirdi → istek toptan reddedilir. Tüm isteği aynı lineId kümesiyle tekrar gönder. |
| append_replay_amount_mismatch | Aynı lineId, farklı tutar. |
| append_replay_method_mismatch | Aynı lineId, farklı ödeme yöntemi (id). |
| too_many_payment_lines | Birleşim sonrası satır sayısı 100'ü aşıyor. |
packet.updated olayını data.changed: "payment_added" ile yayar, "payment_updated" ile değil — payment_updated "ödeme satırları tam değiştirmeyle güncellendi" demektir ve ekleme modunda onu yaymak aboneye "tüm satırlar değişti, yeniden diff'le" dedirtirdi. replace modu eskisi gibi payment_updated yayar.// ekleme başarılı (dokümandaki 100₺ nakit korundu, 240₺ kart eklendi → 340)
{ "success": true, "data": { "packetId": "e2e-append-packet", "paid": 340, "replayed": false } }
// aynı lineId ikinci kez → YAZMA YOK, doküman değişmedi
{ "success": true, "data": { "packetId": "e2e-append-packet", "paid": 340, "replayed": true } }
// hata — gövde `status` alanını da taşır (HTTP kodunun kopyası)
{ "success": false, "status": 400, "message": "append_replay_amount_mismatch" }replayed ⏳ ekleme modu ile birlikte gelir; replace modunda anlamı yoktur.
Hepsi HTTP 200 + { success:false, message } (rate limit 429 hariç). Gerçek mesajlar:
| Kural | message (teyitli) |
|---|---|
lineId biçim ihlali / rezerve / istekte tekrar | Invalid lineId: <id> · Duplicate lineId: <id> (400) |
| İptal edilmiş (void) satırın kimliği yeniden beyan edildi | lineId belongs to a cancelled line (409) |
metadata düz map gönderildi (dizi bekleniyor) | metadata must be an array of {key, value} (400) |
| Geçersiz key/değer, aynı yazarın tekrar eden anahtarı ya da yazar bütçesi (10 öğe / 256 karakter) aşımı | Invalid metadata: payments[i]: <sebep> (400) |
| İstek başına 20 KB ya da belge geneli 100 KB bütçe aşımı | Total metadata size after merge … (yours: X, other plugins: Y) (400) |
Normal satırın id'si tenant'ın yöntemi değil | unknown_payment_method (400) |
| Tenant'ta hiç ödeme yöntemi tanımlı değil | no_payment_methods_configured (400) |
Yeni paid paketin total'ını aşıyor | Paid (999) exceeds total (51.9). |
| Paket bulunamadı | Packet not found |
orders:write onaylı değil | plugin.scope.denied |
İki alan da opsiyoneldir; hiçbiri gönderilmezse davranış bugünküyle aynıdır (mevcut eklentiler etkilenmez). Tam kurallar: Satır Kimliği & metadata.
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| payments[].lineId | string | – | Kararlı satır kimliği — çağıran taşır, platform korur. 8–64 karakter (⏳ alt sınır 1 → 8'e çıktı), alfanümerik başlar, A-Z a-z 0-9 . _ : - içerir — ⚠️ noktalama yalnız İÇERİDE (pay_01H9ABCD geçer; _pay01H9ABCD ve -pay01H9ABCD 400); saf rakamdan oluşan kimlik reddedilir ("1", "42" → 400). kuver ve new rezervedir (büyük/küçük harf farketmez) → 400 Invalid lineId: kuver. Aynı istekteki liste içinde tekrar edemez → 400 Duplicate lineId: <id>; iptal edilmiş (void) bir satırın kimliğini yeniden beyan etmek → 409 lineId belongs to a cancelled line. Gönderilmeyen satıra platform üretir ve üretilen kimlikler beyan edilenlerle çakışmaz. Göndermesen bile her ödeme satırı kalıcı bir lineId alır (platform üretir, pay- önekli — önceden ödeme kaydının kimliği hiç yoktu). ⚠️ Kimlik korunması koşulludur: her istekte yeniden beyan edilmelidir (aşağı bkz.). |
| payments[].metadata | array | – | Opak korelasyon alanı — { key, value } dizisi (düz map değil; map gönderen istek 400 metadata must be an array of {key, value} alır). Platform yorumlamaz: saklar ve okuma yanıtında yazar damgasıyla (by) döner. key alfanümerik başlar, A-Za-z0-9_-, ≤64; value string | number | boolean (iç içe obje / dizi / null → 400). Bütçeler: yazar başına ≤10 öğe / satır ve value ≤256 karakter, istek başına ≤20 KB (sepet ve ödeme ayrı), belge geneli birleşme sonrası ≤100 KB. |
(uuid, lineId) çiftini anahtar yapmaktır. uuid (kalıcı satış kimliği) artık her olayda geliyor ve satış bazında kesin ayrık — çift, bu hata sınıfını kuralın da ötesinde kapatır.payments[].id satır kimliği DEĞİLDİR. O alan ödeme yönteminin kimliğidir (nakit/kart tanımı — bkz. payment-methods/list) ve yerli akışlarda satır uuid'si de taşıyabilir; polimorfiktir, satır silme her yerde onu hedefler. Bu yüzden kalıcı kimlik ayrı lineId alanına yazıldı; id'nin anlamı değişmedi. Okuma yanıtında ödeme satırı lineId (kimlik) ve methodId (yöntem) alanlarını ayrı ayrı döner.metadata (başka eklentilerin yazdıkları dahil) onunla birlikte gider. İstek reddedilmez (meşru satır silme de bu yola düşer) ama platform kaybı loglar. Kimliği kendi kaydından türet ve kendi tarafında sakla — doğru/yanlış örneği.metadata'ya PII yazmayın (ad, telefon, e-posta, adres, kart): sözleşme gereği yasaktır. Platform bu alanda desen taraması yapmaz — yanlış pozitif meşru bir mali kaydı reddedip satışı durdururdu; kural sözleşme düzeyindedir, ihlal eklentinin sorumluluğundadır. Müşteri verisi customers:read + rıza üzerinden alınır.orders:read yeterlidir, müşteri-PII rızası gerekmez ve alan push gövdelerinden silinmez. note, paymentNote ve iptal reason bu kapsamda değildir — onlar gerçek kullanıcı girdisi taşır ve rıza kapısına tabi olmaya devam eder.{ id, title, price, isDiscount?, lineId?, metadata? } gönderirsiniz; geri okurken packets/get (ve tables/get · packet.created) payments[] dizisini { lineId, methodId, title, amount, cash, isDiscount, metadata } olarak döner — price→amount, yazmadaki id (YÖNTEM id'si) → methodId, ayrıca yöntemin cash bayrağı eklenir (nakit/nakit-dışı ayrımı buradan okunur). Satır kimliği ayrı bir alandır: lineId — eski satırlarda null, id'ye fallback yapılmaz.paid ≤ total invariantı geçici bozulabilir. Sıralı çağır. Nihai finansal güvence kapanışta (paid == total).