Masa Ödemeleri — POST /plugin-api/tables/update-payments ✓ Canlı

Dine-in masanın ödeme satırlarını yazar (packets/update-payments'ın masa karşılığı). İki mod: mode:'replace' (VARSAYILAN — TÜM ödemeleri değiştirir) ve mode:'append' (mevcutlara dokunmadan ekler; lineId zorunlu — 🧪 YALNIZ sandbox; production'da yok ve planlanmıyor). Ödeme kuralları paket ucuyla birebir aynı: id'ler tenant'ın gerçek ödeme yöntemlerine karşı doğrulanır. Hiçbir uç masayı KAPATMAZ.

← API Uçları · Masa kalemleri: tables/update-orders · Paket karşılığı: packets/update-payments.

İstek

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

{
  "tableId": "tbl-7a3f...",                       // ZORUNLU — açık masanın doc id'si
  "expectedUuid": "9f3ab27c-...",                  // ops AMA verilirse ZORUNLU eşleşir — oturum kapısı (409 session_changed)
  "mode": "replace",                               // ops — "replace" (VARSAYILAN) | "append"  🧪 append yalnız SANDBOX (prod'da YOK, planlanmıyor)
  "payments": [                                    // ZORUNLU — replace: masanın YENİ ödeme listesi (FULL REPLACE)
    { "price": 268, "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)
  ]
}
  • Scope: orders:write · write rate-limit.
  • mode opsiyonel, varsayılan "replace" — göndermezsen davranış eskisiyle bit-bit aynı.
  • "replace": payments masanın 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.
  • tableId: açık masa doc id'si — tables/open'dan al.
  • expectedUuid: opsiyonel oturum kapısı — gönderirsen masanın o anki oturum uuid'si ile eşleşmek zorundadır; eşleşmezse 409 session_changed ve hiçbir şey yazılmaz. Aşağıdaki bölüme bak — gecikmeli yazan her entegrasyon için pratikte zorunludur.

expectedUuid — oturum kapısı

Gecikmeli yazıyorsan bu alanı GÖNDER — yoksa parayı YANLIŞ MÜŞTERİYE yazabilirsin. Somut senaryo: kart işlemi terminalde ~20 saniye sürüyor. Bu sırada masa kapanıp yeni bir müşteriye açılabilir — kapanış masa dokümanını siler, yeni oturum yeni bir uuid alır. Kapı olmadan, geç gelen onay yeni müşterinin hesabına düşer.
  • Opsiyonel ama verildiğinde zorunlu eşleşir: göndermezsen kapı çalışmaz; gönderdiğin değer tutmuyorsa istek 409 session_changed alır ve yazma hiç yapılmaz.
  • Değeri nereden alırsın: ödeme terminali sağlayıcısıysan komut gövdesinde write.expectedUuid olarak gelir — satır yazarken onu geri ver. Kendi akışını kuruyorsan masanın okuma yanıtındaki oturum uuid'sini taşı.
  • Gerçek ortamda doğrulandı: oturum değişince yazma reddediliyor ve yeni müşterinin hesabına hiçbir şey yazılmıyor.

Ekleme modu — mode: "append"

🧪 YALNIZ SANDBOX — production'da YOK ve prod'a alınması PLANLANMIYOR (ürün kararı); geçici bir bekleyiş değil, kalıcı durum. Bu uç sandbox.plugins.restomenum.app üzerinden gerçek çağrılarla doğrulandı (9/9): masadaki nakit satırı korundu (100 + 150 = 250), tekrar gönderimde ikinci satır oluşmadı, replayed:true döndü. Paket ucuyla sözleşme paylaşımı artık varsayım değil, ölçüm. Prod'da mode göndermek isteği komple 400'e düşürür ("mode" is not allowed), eski davranışa düşmez → production entegrasyonunda gönderme. Durum değişirse değişiklik günlüğünden duyurulur.

Sözleşme packets/update-payments ile birebir aynıdır — yalnız kimlik alanı değişir (tableId). Gerekçe, lineId zorunluluğu, replayed ve hata kodlarının tamamı için o sayfayı oku; burada özet:

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

{
  "tableId": "tbl-7a3f...",
  "mode": "append",                                // mevcut satırlara DOKUNMAZ — üzerine ekler
  "payments": [
    { "price": 268, "id": "m-card", "lineId": "pay-term-01H9..." }   // lineId ZORUNLU (tekrar gönderim kalkanı)
  ]
}
  • Gönderilen satırlar mevcutların üzerine eklenir; mevcut satırlara dokunulmaz (onları tekrar gönderme).
  • lineId ZORUNLU — tekrar gönderim kalkanı: kimliksiz satıra platform her çağrıda farklı kimlik üretir, ağ hatasındaki retry ikinci satır olur = çift tahsilat.
  • lineId GÜÇLÜ olmalı — en az 8 karakter, saf rakam değil ("00000001" reddedilir). Grandfather affı ekleme modunda İŞLEMEZ: af yalnız dokümanda zaten var olan kimlikleri bağışlar. Kalıp: pay-01H9XYZ… (önek + uuid).
  • paid birleşim üzerinden yeniden hesaplanır; paid ≤ total kapısı geçerli. Kapatma yok.
  • İki tavan: istek başına ≤ 20 satır (şema, her iki modda); ekleme modunda birleşim ≤ 100.
  • Yanıttaki replayed: true = aynı lineId ile tekrar gönderim, yazma yapılmadı, event yayılmadı.
  • Hatalar (400): append_requires_line_id · append_requires_payment_line · append_partial_duplicate · append_replay_amount_mismatch · append_replay_method_mismatch · too_many_payment_lines (birleşim > 100 satır).
Webhook: ekleme modu table.updated'ı data.changed: "payment_added" ile yayar; replace modu eskisi gibi "payment_updated". Tekrar gönderimde (replayed) hiçbir event yayılmaz.

Yanıt

// ekleme başarılı (masadaki 100₺ nakit KORUNDU, 150₺ eklendi → 250)
{ "success": true, "data": { "tableId": "e2e-table-A2", "paid": 250, "replayed": false } }

// aynı lineId ikinci kez → yazma YOK, ikinci satır oluşmadı
{ "success": true, "data": { "tableId": "e2e-table-A2", "paid": 250, "replayed": true } }

Ödeme yöntemi doğrulaması (packets ile birebir)

Kurallar packets/update-payments ile aynı. Önce payment-methods/list (payment_methods:read).

SatırKural
Normal (isDiscount yok/false)id tenant'ın yöntemi OLMALI. title/cash/noreport yöntem kaydından türetilir (gönderilen title yok sayılır).
İndirim (isDiscount:true)Doğrulamadan muaf (serbest id); title zorunlu.

Kurallar & hatalar

Durummessage
Normal satır id'si tenant'ın yöntemi değilunknown_payment_method (400)
Tenant'ta hiç ödeme yöntemi yokno_payment_methods_configured (400)
Yeni paid total'ı aşıyorPaid (X) exceeds total (Y). (400)
lineId biçim ihlali / rezerve / istekte tekrarInvalid lineId: <id> · Duplicate lineId: <id> (400)
İptal edilmiş (void) satırın kimliği yeniden beyan edildilineId 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)
Geçersiz gövdejoi doğrulama mesajı (400)
Masa yok / kapanmışTable not found (404)
⏳ Ekleme moduna özgü (yukarıdaki bölüm)append_requires_line_id · append_requires_payment_line · append_partial_duplicate · append_replay_amount_mismatch · append_replay_method_mismatch · too_many_payment_lines (400)

payments[] — satır kimliği (lineId) & metadata

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

AlanTipZorunluAçıklama
payments[].lineIdstring–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[].metadataarray–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.
⏳ Grandfather — PLANLANAN davranış, bugün ETKİN DEĞİL. Af mantığı platformda yazılı ama şema katmanı isteği daha transaction'a girmeden reddettiği için ona ulaşılmıyor — dört yazma ucunun hepsinde ölçüldü. Bugün dokümanda zaten var olan zayıf bir kimlik de 400 alır. Ayrıntı: Satır kimliği → Grandfather. Aşağıdaki akış tasarlanan davranıştır:

Tam-sepet değiştirmede önceki yanıtta aldığın kimlikleri geri gönderirsin; kural konmadan önce yazılmış zayıf bir kimlik dokümanda duruyorsa onu reddetmek ilgisiz satırlar dahil tüm isteğini düşürür ve dokümanı kalıcı kilitlerdi. Bu yüzden:
1️⃣ Biçim geçersizse her zaman 400 — asla affedilmez.
2️⃣ Biçim geçerli ama zayıfsa (<8 karakter ya da saf rakam): kimlik dokümanda zaten varsa kabul edilir (grandfather), yoksa 400 (yeni zayıf beyan).
Kontrol transaction içinde, doküman zaten okunurken yapılır — ek okuma maliyeti yok.
⚠️ packets/create ve tables/create uçlarında grandfather YOKTUR: yeni satış açarlar, karşılaştırılacak mevcut doküman yoktur → her zayıf kimlik yeni beyandır ve 400 alır.
Öneri: anahtarını çifte çevir. Kural bir kalkan; yapısal çözüm (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.
Altın kural: kimliği HER istekte yeniden beyan et. Bu uç tam değiştirme yapar — platform yalnız gelen listedeki kimlikleri dokümandakilerle eşler. Beyan edilmeyen satır "silinmiş" sayılır ve üzerindeki tüm 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.
Görünürlük: alan tenant içi korelasyon verisidir (kiosk yazar, fiskal sağlayıcı okur) → her kanalda 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.
Yazma ve okuma satır şekli FARKLIDIR. Buraya { id, title, price, isDiscount?, lineId?, metadata? } gönderirsiniz; tables/get ise payments[] dizisini { lineId, methodId, title, amount, cash, isDiscount, metadata } olarak döner (price→amount, yazmadaki id = YÖNTEM id'si → methodId, artı yöntemin cash bayrağı — nakit/nakit-dışı ayrımı buradan okunur). Satır kimliği ayrı alandır (lineId; eski satırlarda null).