customer.* PIIMüşteri (CRM) yaşam döngüsü ve cari hesap (veresiye/tahsilat) event'leri. Dördü de eklentinin webhookUrl'ine async imzalı POST edilir, customers:read scope + events:subscribe ister. PII içerir — customers:read + tenant consent gerektirir; aksi halde içerik maskelenir/boş gelir.
| Event | Tetik | Durum |
|---|---|---|
| customer.created | Müşteri dokümanı ilk kez yazıldığında (lifecycle) | ✓ Canlı |
| customer.updated | Mevcut müşteri her güncellendiğinde (onWrite; tam snapshot, diff değil) | ✓ Canlı |
| customer.order_added | Cari hesaba sipariş yazıldığında (veresiye/açık hesap) | ✓ Canlı |
| customer.payment_added | Cari hesaptan tahsilat yapıldığında | ✓ Canlı |
customer.order_added TETİKLEMEZ — bu event yalnız açık "hesaba sipariş yaz" (veresiye) eylemi içindir (cari bakiyeyi değiştirir). Aynı şekilde ters işlemler (sipariş geri alma / ödeme iptali) hiçbir event tetiklemez. Normal satışları table.* / packet.* event'lerinden izle; double-count etme.events: ["customer.created", "customer.updated", "customer.order_added", "customer.payment_added"] + events:subscribe.POST {webhookUrl}
Content-Type: application/json
X-Restomenum-Signature: t=<unixSec>,v1=<HMAC_SHA256(webhookSecret, "<t>.<rawBody>")>
X-Restomenum-Event: customer.created | customer.updated | customer.order_added | customer.payment_added
X-Restomenum-Delivery: <deliveryId> // teslim-bazlı (debug) — idempotency anahtarı DEĞİL"<t>.<rawBody>"), ±5 dk replay penceresi — bkz. imza şeması. Webhook endpoint'in HTTPS olmalı (private/loopback reddedilir — SSRF).{ id, type, version, tenantId, occurredAt, data }. version string "1"; occurredAt epoch milisaniye (imza t= ise saniye); tenantId = restoran ID'si.id ile dedup (at-least-once → aynı id tekrar gelebilir; ikinci kez işleme, hızlı 2xx dön). X-Restomenum-Delivery teslim-bazlı debug header'ıdır — idempotency anahtarı olarak kullanma (her teslimde farklıdır → retry'ı asla yakalamaz).2xx → işlendi. 5xx/timeout → retry → dead-letter.Müşteri PII'si yalnız iki koşul birden sağlanınca paylaşılır: PII scope (customers:read) VE tenant consent'i (dataConsent.piiShared === true, kesin boolean — 1/"true"/"yes" reddedilir).
| Durum | Webhook davranışı |
|---|---|
customers:read yok | Envelope yine teslim edilir, ama data tamamen boş ({}) — event'in varlığını/id'sini bilirsin, içeriğini görmezsin. |
| Scope var, consent yok | customer → yalnız { id } (+ region varsa — non-PII). PII (name/phone/address) + total + serbest-metin (note) düşer. |
| Scope + consent | Tam customer objesi (PII dahil) + tüm alanlar. |
data={} bırakır. Yani "scope yok → {}" (webhook) ile "scope yok → 403" (customers/get) aynı değildir. Yalnız consent katmanı (scope var, consent yok → { id }, region varsa eklenir) iki tarafta da aynıdır.Redaction iki katmandır: (1) customer objesi allowlist ile { id }'a indirgenir (region varsa korunur — non-PII; yeni alanlar consent yoksa otomatik düşer — güvenli); (2) diğer iç içe nesneler (örn. orders[], ödeme method) alan-adı bazlı recursive temizlenir (name/phone/address/email/tckn/vergino+ serbest-metin note/paymentNote/description/aciklama/desc silinir; orders[].id/lineTotalgibi yapısal alanlar korunur). Tam model: customers:read.
data = { customerId, customer }. customerId opak referanstır (consent'ten bağımsız akar); customer ise customers/getile aynı allowlist şeklidir ve consent ile redact edilir. customer.updated tam güncel snapshot taşır — değişen-alan (diff) listesi içermez (bugün changedFields yok; ileride eklenebilir). Diff istiyorsan önceki state'i kendin cache'le.
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| id | string | ✓ | Müşteri ID'si — her zaman gelir, asla null değil. |
| region | string | null | – | Bölge/şube etiketi. PII değildir (kaba coğrafi referans) → consent olmadan da akar. Opsiyonel: müşteride tanımlıysa gelir, aksi halde alan hiç gelmez (canlı örneklerde yoktu). |
| name | string | null | – | PII — müşteri adı. Yalnız consent ile; ayarlanmadıysa null. |
| phone | string | null | – | PII — telefon. Yalnız consent ile; ayarlanmadıysa null. |
| address | string | null | – | PII — adres. Kaynakta adress (typo) veya address olabilir; dışa daima address. Yalnız consent ile. |
| total | number | ✓ | Ömür boyu toplam harcama (CRM/sadakat) — ONDALIK TL (kuruş değil; ör. 22.61). Finansal — PII değil ama consent yoksa düşer. Asla null değil — geçersiz değer → 0. |
kalan, paid, log, closed, beforeClosed, groupId/group*, category, new — consent verilmiş bir eklentiye dahi sızmaz.{
"id": "evt_<deterministik-firestore-trigger-id>",
"type": "customer.created",
"version": "1",
"tenantId": "<tenantId>",
"occurredAt": 1781000000000,
"data": {
"customerId": "cust_001",
"customer": {
"id": "cust_001",
"name": "Ahmet Yılmaz",
"phone": "5551234567",
"address": "Atatürk Cad. No:5",
"total": 0
}
}
}customer.updated aynı şekildir, yalnız type + güncel total farklı. Consent yoksa customer → { id } (region varsa eklenir); scope yoksa data → {}.
✓ CanlıMüşterinin cari hesabına sipariş yazılınca (total += tutar, kalan = total − paid).
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| customerId | string | ✓ | Müşteri ID (opak; consent'siz akar). |
| customer | object | ✓ | § müşteri nesnesi (consent ile redact). |
| orders | object[] | ✓ | Hesaba yazılan sipariş satırları — müşteri-event satırı: id, title, quantity, options[], extra, note, lineTotal (note VAR, discount YOK). Paket/masa satırından FARKLI (packets/get: discount var, note yok). lineTotal ONDALIK TL, platform hesaplar. |
| amount | number | ✓ | Bu işlemde eklenen toplam tutar (ondalık TL). |
| balance | number | ✓ | İşlem sonrası kalan borç (customer.total − customer.paid). Muhasebe özeti — PII redaction'dan bağımsız daima gönderilir. |
{
"id": "cust_001_addorder_order_line_001order_line_002",
"type": "customer.order_added",
"version": "1",
"tenantId": "<tenantId>",
"occurredAt": 1781000000000,
"data": {
"customerId": "cust_001",
"customer": {
"id": "cust_001",
"name": "Ahmet Yılmaz",
"phone": "5551234567",
"address": "Atatürk Cad. No:5",
"total": 46.41
},
"orders": [
{ "id": "order_line_001", "title": "Espresso", "quantity": 1, "options": [], "extra": 0, "note": "", "lineTotal": 9.9 },
{ "id": "order_line_002", "title": "Cortado", "quantity": 1, "options": [], "extra": 0, "note": "", "lineTotal": 13.9 }
],
"amount": 23.8,
"balance": 36.41
}
}<customerId>_addorder_<sıralı satır id'leri, ayraçsız> — aynı işlem aynı id'yi belirlenimci üretir; dedup için kullan. note serbest-metindir → consent yoksa silinir.✓ CanlıCari hesaptan ödeme/tahsilat yapılınca (paid += tutar). Tutar ≤ 0 ise event tetiklenmez.
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| customerId | string | ✓ | Müşteri ID (opak; consent'siz akar). |
| customer | object | ✓ | § müşteri nesnesi (consent ile redact). total ödeme ile değişmez. |
| amount | number | ✓ | Ödeme tutarı. |
| method | object | null | – | Ödeme yöntemi { id, title } (ör. "Nakit"). İç flag'ler (noreport/cash) çıkarılmıştır. |
| balance | number | ✓ | Ödeme sonrası kalan borç (total − paid). Daima gönderilir. Kendi kayıtlarınla karşılaştır; uyuşmazlık → bize bildir. |
| orders | string[] | ✓ | Toplu tahsilat → [] (lump-sum). Kalem-bazlı → ödenen satır ID'leri. |
| note | string | – | Ödeme notu (serbest-metin → consent yoksa silinir). |
{
"id": "cust_001_addpayment_pay_77",
"type": "customer.payment_added",
"version": "1",
"tenantId": "<tenantId>",
"occurredAt": 1781000000000,
"data": {
"customerId": "cust_001",
"customer": {
"id": "cust_001",
"name": "Ahmet Yılmaz",
"phone": "5551234567",
"address": "Atatürk Cad. No:5",
"total": 46.41
},
"amount": 36.41,
"method": { "id": "a2-cash", "title": "nakit" },
"balance": 0,
"orders": [],
"note": ""
}
}<customerId>_addpayment_<sıralı ödeme id'leri>; kalem-bazlı payorders_<paymentId>_<ödenen satır id'leri> (ayraçsız birleşik). Aynı işlem aynı id → dedup et.customer.deleted event'i yoktur — müşteri dokümanı silindiğinde bu kanaldan bildirim almazsın. Eklentini silme bildirimi gelmeyecek varsayımıyla tasarla; silinen müşterileri customers/list'i periyodik çalıştırıp artık dönmeyenleri temizleyerek mutabık kıl.GDPR/KVKK silme propagasyonu ayrıdır: tenant bir müşteriyi silince, PII consent'li kurulumlar zorunlu customer.redact lifecycle webhook'unu alır → o customerId'ye ait tüm PII'yi kendi tarafında silmelisin. Bu, abone olunan bir customer.* event'i değil, her bağlı kuruluma düşen bir lifecycle sinyalidir.