Sistem Mimarisi
Çok-tenant (SaaS) marketplace: müşteri, satıcı ve system istemcilerinden core servislere uzanan yapı; kurye dış sağlayıcı connector ile
Mimari Analiz & Ürün Yönü
Ekibe ürünün nasıl ilerleyeceğini aktaran kararlar ve standartlar
0 · Genel Yaklaşım
Bu proje yalnızca backend kapsamındadır. Müşteri App ve Satıcı Panel gibi frontend'ler ayrı değerlendirilecek; kurye operasyonu dış sağlayıcılara (KuryeMaxi vb.) devredilir. Bu referans REST API sözleşmesini ve mimari kararları tanımlar.
Hedef, yüksek trafikli bir marketplace (Yemeksepeti/Getir muadili) backend'idir. Üç aktör vardır: customer user (satıcı sahibi · temsilci) system (super_admin · ops · finance · content · support) — her biri ayrı API bölümünde ve kendi kimlik/yetki katmanında.
SaaS / Çok-Tenant model
Fan Yemek App tek bir marketplace değil, SaaS platformudur: altında birden çok bağımsız marketplace/brand (Tenant — ör. Fan Yemek, Gıs Yemek, Ts Yemek) yayınlanır. Tenant'ları platform ekibi (system) oluşturup yönetir; her tenant kendi host/branding'ine sahiptir.
- Satıcılar global bir hesaptır; hangi marketplace'te yayına çıkmak istiyorsa başvuru gönderir (
SellerTenant), system onaylar. Komisyon oranı tenant bazlıdır (SellerTenant.commissionRate, varsayılanTenant.defaultCommissionRate). - Her tenant kendi ödeme altyapısını
tahsilat-appüzerinden tanımlar (TenantPaymentConfig); online tahsilat tenant merchant ref ile yönlendirilir. - Satıcı menüleri
MenuDefinitionolarak tanımlanır; onay sonrası istenen tenant(lar)da yayına alınır (MenuDefinitionPublication— çoka-çok). - Ürünler dinamik kategoriden bağımsız, sabit bir
MenuType(menü türü) ile sınıflandırılır — mağaza içeriğini belirlemek için. Menü hiyerarşisi: Kategori → Ürün.
Detaylı kapsam karşılaştırması ve "neden ayrı uygulama yazmalıyız" gerekçeleri için bkz. fan-yemek-kapsam-analizi.md.
1 · Aktörler & Rol Yönetimi (RBAC Best Practices)
Aktör = API bölümü; rol = aktör içindeki yetki katmanı. seller aktörü satıcı (Seller) sahibi (owner) ve temsilci rollerini barındırır. system aktörü super_admin, ops, finance, content ve support rollerini barındırır — bir system kullanıcısı birden fazla role sahip olabilir. tenant aktörü tenant'a ait salt-okunur raporlama kullanıcılarıdır (kendi tenant kapsamı). Kurye operasyonu dahili uygulama değil; dış sağlayıcı connector ile yürütülür.
Tenant kapsamı: system aktörü, platform genelinde marketplace/tenant'ları ve MenuType katalogunu yönetir. Müşteri istekleri aktif tenant kapsamındadır (host veya X-Tenant ile çözümlenir). Satıcının bir tenant'ta görünmesi için önce yayın başvurusunun, menüsünün görünmesi için menü yayınının system onayından geçmesi gerekir. Tenant aktörü yalnız kendi tenant'ının raporlarını salt-okunur görür.
| Aktör / Rol | Prefix | Erişebildiği başlıca alanlar |
|---|---|---|
| customer | /v1 | Keşif, sepet, sipariş, ödeme, profil — yalnızca kendi verisi (telefon OTP ile giriş/kayıt) |
| seller · owner | /v1/seller | Tam satıcı (Seller) yönetimi: profil, menü, sipariş, analiz, finans, teslimat dispatch (telefon OTP zorunlu) |
| seller · temsilci | /v1/seller | Yalnızca sipariş operasyonu (durum güncelleme); menü/finans/profil kapalı (telefon OTP zorunlu) |
| tenant · reporting | /v1/tenant-panel | Yalnız kendi tenant'ına ait salt-okunur raporlar/dashboard (sipariş, ciro, satıcı performansı, komisyon özeti); yazma yok (telefon OTP zorunlu) |
| system · super_admin | /v1/system | Tam platform erişimi: tüm endpoint'ler, sistem config yazma, audit-log (telefon OTP zorunlu) |
| system · ops | /v1/system | Operasyon: kullanıcı/satıcı yönetimi, onay/askıya alma, kurye sağlayıcıları, sipariş izleme, audit-log okuma |
| system · finance | /v1/system | Finans: iade, payout, komisyon güncelleme, finans özeti, satıcı/sipariş salt okunur |
| system · content | /v1/system | İçerik: mutfak kategorileri CRUD, kupon/kampanya, toplu bildirim |
| system · support | /v1/system | Destek: kullanıcı/sipariş görüntüleme, hesap askıya alma — GDPR silme ve config yazma kapalı |
Best practice özeti
- Aktör başına ayrı token/scope: customer token'ı panel uçlarına geçemez. Access JWT claim'leri:
act(customer | seller | tenant | system),roles[], satıcı içinsellerId, tenant içintid, oturum içinsid/jti/ver, hedef API içinaud. Detaylı izinler token'da değil; rol → permission map sunucuda türetilir. - En az ayrıcalık (least privilege): her rol yalnızca ihtiyaç duyduğu yetkiye sahip. Yeni rol eklenebilir olmalı (rol → izin eşlemesi konfigüratif). system aktörü çoklu rol destekler;
super_adminimplicit olarak tüm system endpoint'lerine erişir. - Endpoint düzeyinde policy: yetki kontrolü gateway/filter + servis katmanında çift kademeli; ayrıca resource ownership kontrolü (satıcı X'in temsilcisi sadece X'in siparişlerini görür).
- Kısa ömürlü access + refresh token rotation; anında iptal için
ver+ RedisINCR auth:ver:{sub}birincil mekanizma;blacklist:access:{jti}yalnızca opsiyonel (tek cihaz logout, kalan access TTL kadar). - Telefon OTP zorunlu (customer, seller, tenant, system): giriş ve kayıt
phoneNumberile; varsayılan kanal WhatsApp (app.monochat.ai), alternatif SMS (Twilio). TOTP/authenticator app kullanılmaz. - Birincil kimlik:
phoneNumber+actorbirlikte unique — aynı numara farklı panellerde ayrı hesap olabilir. - system aktörü hassas işlemleri
audit-log'a yazılır.
Not: API ağacında ilgili uçlarda Gerekli rol rozetiyle hangi rolün erişebildiği işaretlenmiştir.
1b · JWT & Oturum Mimarisi (OTP → Token Döngüsü)
Klasik şifre login yok; OTP verify = login. Akış: otp/send → otp/verify → accessToken + refreshToken → refresh (rotation) → logout. Access token kısa ömürlü JWT (RS256/ES256, 10–15 dk); refresh token uzun ömürlü opaque string (7–30 gün, Redis'te saklanır).
Token modeli
| Token | Format | Ömür | Taşıma |
|---|---|---|---|
| accessToken | JWT | 10–15 dk | Authorization: Bearer |
| refreshToken | Opaque | 7–30 gün | HttpOnly cookie (web) / secure storage (mobil) |
Access token claim şeması
{
"sub": "user-uuid",
"act": "customer | user | system",
"roles": ["owner"],
"sellerId": "seller-uuid",
"sid": "session-family-uuid",
"jti": "access-token-uuid",
"ver": 3,
"iss": "fan-yemek-auth",
"aud": "customer-api | seller-api | system-api",
"iat": 1710000000,
"exp": 1710000900
}
| Aktör | Zorunlu claim'ler |
|---|---|
| customer | sub, act, sid, jti, ver, aud |
| seller | + roles (owner | representative), + sellerId |
| tenant | + tid (tenant kapsamı) — salt-okunur raporlama |
| system | + roles[] (super_admin, ops, finance, content, support) |
JWT'ye koy / koyma
| Bilgi | JWT'de? | Neden |
|---|---|---|
| sub, act, roles, sellerId | Evet | Authorization |
| sid, jti, ver | Evet | Session / anında iptal — ver Redis auth:ver:{sub} ile eşleşmeli |
| Telefon, ad, e-posta | Hayır | PII — verify response / /users/me |
| Tenant | Hayır | Host / X-Tenant ile çözülür |
| Detaylı permissions | Hayır | Rol → izin map sunucuda türetilir |
OTP + JWT akışı
Yetkilendirme katmanları
- Gateway/filter: JWT imza + TTL doğrula;
act+audroute prefix ile eşleşsin; ardındanverRedis kontrolü (endpoint policy'sine göre fail-open / fail-closed). - Servis katmanı: rol kontrolü + resource ownership (
sellerId === order.sellerId). - Aktör izolasyonu: customer token
/v1/seller/*ve/v1/system/*uçlarına geçemez.
İptal mekanizmaları
| Mekanizma | Ne zaman | Nasıl |
|---|---|---|
ver (Redis version) |
Rol değişimi, askıya alma, force logout, refresh reuse sonrası oturum iptali | INCR auth:ver:{sub} — tüm cihaz/sekmeler anında düşer |
| Refresh session | Logout, rotation, reuse detection | Mevcut refresh:{tokenHash} + sid ailesi |
| Access TTL | Her istek | 10–15 dk; imza doğrulaması Redis'e gitmeden yapılır |
| Blacklist (opsiyonel) | Tek cihaz logout | blacklist:access:{jti} TTL = kalan access süresi |
Redis aynı VPS/local ağda round-trip ~1 ms; tek INCR ile blacklist'e göre çok daha az operasyon. OTP verify / refresh sırasında GET auth:ver:{sub} (yoksa 0) → JWT ver claim'ine yazılır.
Access token doğrulama (gateway middleware)
async function verifyAccessToken(req, policy = 'fail-open') {
const decoded = jwt.verify(req.token, PUBLIC_KEY, { aud, iss });
try {
const current = await redis.get(`auth:ver:${decoded.sub}`);
if (current !== null && parseInt(current, 10) !== decoded.ver) {
throw unauthorized('TOKEN_STALE');
}
} catch (err) {
if (err.code === 'TOKEN_STALE') throw err;
// Redis erişilemez
if (policy === 'fail-closed') {
throw serviceUnavailable('AUTH_STORE_UNAVAILABLE');
}
// fail-open: log + devam
}
return decoded;
}
Korunan tüm route'larda standart auth hataları: 401 TOKEN_STALE (eski ver), 503 AUTH_STORE_UNAVAILABLE (fail-closed route + Redis down).
Redis erişilemezlik politikası
| Politika | Ne zaman | Redis down davranışı |
|---|---|---|
| fail-open (varsayılan) | Müşteri keşif, sepet, sipariş okuma, satıcı panel okuma | İstek devam eder; kısa pencerede eski ver kabul edilir |
| fail-closed | /v1/system/* yazma, ödeme/checkout, hesap askıya alma, rol atama |
503 AUTH_STORE_UNAVAILABLE — güvenlik öncelikli |
Global tek davranış yerine route metadata / guard seviyesinde securityPolicy: 'strict' | 'standard' ile ayrılır.
Redis anahtarları
otp:{sessionId}→ OtpChallenge (mevcut)auth:ver:{sub}→ integer counter (TTL yok; kalıcı, ~8 byte/kullanıcı)refresh:{tokenHash}→{ sub, act, sid, ver, expiresAt }blacklist:access:{jti}→ opsiyonel,veryaklaşımına göre ikincil; logout sonrası kalan access TTL kadar
1c · Integration API (3. Parti — Faz 2+)
Faz 1 kapsamı dışı
Integration API Faz 1'de implement edilmez; partner onboarding ve dış istemci erişimi yoktur. Bu bölüm ilerideki entegrasyonlar (ör. resto-maxi-app kanal bağlantısı) için tasarım notudur.
3. parti uygulamalar (POS, entegratör, ERP) makine-to-machine istemcidir. OTP veya kullanıcı oturumu kullanılmaz; kimlik integration client credentials (clientId + clientSecret) ile doğrulanır. Inbound webhook'lar (/webhooks/*, HMAC imza) bu bölümden ayrıdır — bkz. Platform Webhook / Kurye Connector.
Kimlik akışı (apiKey → JWT)
clientId + clientSecret → token → Bearer JWT → (refresh rotation)
- System/partner onboarding sonrası entegratöre
clientId+clientSecretverilir (secret hash'lenerek saklanır). POST /v1/integration/auth/token— credential exchange.- Yanıt:
accessToken(JWT) +refreshToken(opaque) — Bölüm 1b'deki rotation mantığı geçerlidir. - Sonraki istekler:
Authorization: Bearer {accessToken}. - Süresi dolunca:
POST /v1/integration/auth/refresh(Faz 2+).
Access JWT claim'leri: act=integration, aud=integration-api, scopes[]; isteğe bağlı sellerId / tenantId (client kaydına bağlı kapsam). OTP/telefon yok.
Token alma — endpoint (Faz 2+)
Ham clientSecret yalnızca token exchange'de gönderilir; her API isteğinde taşınmaz.
POST /v1/integration/auth/token
Body: { "clientId": "...", "clientSecret": "..." }
200 OK:
{
"accessToken": "...",
"refreshToken": "...",
"expiresIn": 900,
"tokenType": "Bearer"
}
401 — Geçersiz credential
403 — Client askıda / süresi dolmuş
429 — Rate limit
2 · Faz Planı
Faz 1 — İlk öncelik
Sağlıklı bir Müşteri App (mobil/web) ve Satıcı Panel (mobil/web) altyapısı. İçerik: kimlik + RBAC, satıcı/menü katalog, keşif & arama, sepet, checkout, sipariş + statü makinesi, satıcı sipariş yönetimi ve tahsilat-app üzerinden ödeme akışı.
Sonraki fazlar (öneri)
- Faz 2 — Kurye Connector & Tracking: dış kurye sağlayıcı entegrasyonu (KuryeMaxi, MuditaKurye vb.), outbound createDelivery + inbound webhook, müşteri teslimat takibi (normalize statü + trackingUrl).
- Faz 3 — System & Finans: kullanıcı/satıcı yönetimi, kurye sağlayıcı konfigürasyonu, komisyon/payout, iade, kupon/kampanya, broadcast, tenant kullanıcı yönetimi + tenant raporlama paneli (salt-okunur). Faz 3 öncesi:
nestjs-accountantPOC (Bölüm 10); başarısızsa custom ledger. - Faz 4 — Gözlemlenebilirlik & ölçek sertleştirme: tracing/metrics/logs, arama (Elasticsearch) ve event akışlarının (Kafka/RabbitMQ) yaygınlaştırılması.
Tam faz dökümü için: fan-yemek-kapsam-analizi.md → Bölüm 7.
3 · Entegrasyonlar
Ödeme — tahsilat-app (tenant bazlı)
Tüm online tahsilat tahsilat-app üzerinden alınır; her tenant kendi merchant/APM tanımını tahsilat-app'te yapar (TenantPaymentConfig). Fan Yemek credentials tutmaz — yalnızca tenantId → tahsilatMerchantRef ile yönlendirir. Kapıda nakit/yemek kartı tahsilat-app'ten geçmez.
Kurye — Dış Sağlayıcı Connector
Kurye operasyonu proje dışında yürütülür (KuryeMaxi, MuditaKurye, Paketiniz vb.). Fan Yemek yalnızca gönder / al modeli kullanır: sipariş ready olunca connector createDelivery çağırır; sağlayıcı webhook ile durum bildirir. Dahili Kurye App yoktur.
Kimlik — MonoChat WhatsApp
Tüm aktörler (customer, seller, tenant, system) için OTP gönderiminin varsayılan kanalı. Fan Yemek auth servisi app.monochat.ai üzerinden WhatsApp OTP tetikler; OTP challenge Redis'te TTL ile tutulur.
Kimlik — Twilio SMS
Kullanıcı channel: sms seçerse OTP Twilio üzerinden SMS olarak gönderilir. WhatsApp ve SMS aynı OTP akışını paylaşır; yalnızca iletim kanalı değişir.
Finans defteri — double-entry ledger
Komisyon split, satıcı hakediş birikimi ve iade kayıtları ledger katmanında tutulur (tahsilat-app yalnızca kart/APM tahsilatı). Birincil aday: @vynelix/nestjs-accountant; yedek: custom TypeORM ledger. Detay: Bölüm 10 · Billing & Ledger Kararı.
4 · Ölçeklenebilirlik (yüksek trafik)
Proje yoğun trafik bekliyor; servisler yatayda (paralel) ölçeklenebilir olmalı: stateless uygulama instance'ları, oturum/cache dışarıda (Redis), uzun işler kuyruğa. Aşağıdaki yapılar temelde tasarıma dahil edilmeli:
Redis
Cache, oturum/token, sepet, teslimat durumu, rate limit sayaçları.
Kafka / RabbitMQ
Sipariş event'leri, bildirim ve tahsilat işlemlerinin asenkron işlenmesi, servis ayrıştırma.
Elasticsearch
Satıcı/menü arama ve q serbest aramasının düşük gecikmeyle karşılanması.
Başlangıçta net modül sınırlarına sahip bir modüler monolit; trafik arttıkça kritik modüller (sipariş, arama, teslimat) bağımsız servislere ayrılabilir. Geospatial sorgular için PostgreSQL + PostGIS.
5 · API Arayüz Standartları
Filtreleme — her zaman queryParam
Filtreler gövdede değil, daima query parametresi olarak gelir. q tüm tablolar için ön tanımlı alanlarda serbest arama yapar.
GET /api/v1/products?q=makarna
GET /api/v1/orders?orderDate.gt=100&orderDate.lt=200
GET /api/v1/sellers?rating.gte=4&sort=-rating&page=2&limit=20
Operatör eki sözleşmesi: .gt .gte .lt .lte .in .ne · sıralama: sort=alan / sort=-alan (azalan).
Liste yanıtı — standart pagination zarfı
Tüm list endpoint'leri sayfalama bilgisini veri içinde döner:
{
"results": [ /* ... */ ],
"options": {
"paginations": {
"currentPage": 1,
"pageItems": 10,
"totalCount": 100,
"totalPage": 10
}
}
}
Hata yanıtı — tutarlı format
Tüm hatalar aynı zarfta döner; istemciler tek bir şemaya göre işler:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Geçersiz istek",
"details": [ { "field": "phoneNumber", "message": "Zorunlu" } ]
}
}
Standart HTTP kodları kullanılır (2xx başarı, 4xx istemci, 5xx sunucu). Aynı standart her iki katmanda (customer ve seller/satıcı) ortak uygulanır.
6 · Kategori Modeli (Rakip Analizi & Karar)
"Kategori" birden fazla kavram için kullanılıyor. Fan Yemek'te bunlar ayrı domain entity olarak tutulur; ek olarak dinamik kategoriden bağımsız, platform yönetimli sabit bir menü türü (MenuType) sınıflandırması vardır.
| Katman | Entity | Amaç | Kim yönetir? | Model |
|---|---|---|---|---|
| Platform | CuisineCategory | Keşif, filtre, ana sayfa ikonları | System | System yönetimli katalog — satıcı serbest metin giremez |
| Platform | MenuType | Ürün sınıflandırma (mağaza içeriği) — dinamik kategoriden bağımsız | System | System yönetimli sabit lookup — satıcı üründe seçer |
| Satıcı | MenuCategory | Menü bölümleme (tek seviye kategori) | Satıcı sahibi | Tam dinamik — düz liste, parentId yok |
Rakip platform özeti
Yemeksepeti
Başvuruda mutfak türü seçilir; sonradan destek talebiyle değişir. Müşteri app'te mutfak filtresi. Market tarafında parent/child hiyerarşi var; satıcı mutfağı pratikte düz liste.
GetirYemek
30+ mutfak kategorisi. Partner API'de /cuisines CRUD + görünürlük flag'leri + satıcı toplu atama. Menü kategorileri satıcı panelinde dinamik.
MigrosYemek
Başvuruda mutfak türü; store modelinde embeddedCuisine. Menü kategori/fiyat satıcı panelinden girilir.
Fan Yemek kararı — Hibrit model
CuisineCategory: system CRUD, sıralama, ikon/görsel, aktif/pasif, görünürlük flag'leri. Kodda hardcoded enum yok — seed migration ile başlangıç.- Satıcı ↔ cuisine çoklu ilişki (örn. Pizza + İtalyan); onay akışında system atar.
MenuType: system yönetimli sabit lookup (CuisineCategory ekranına paralel CRUD). Ürünü dinamik kategoriden bağımsız sınıflandırır (mağaza içeriğini belirler); satıcı üründe zorunlu seçer.MenuDefinition: satıcı menülerini tanım olarak kurar (Kategori → Ürün) ve onay sonrası istediği tenant(lar)da yayına alır (MenuDefinitionPublication, çoka-çok).MenuCategorytek seviyeli düz listedir — iç içe kategori yok; platform system yalnızca yayını onaylar.
Önerilen seed mutfak listesi (~30)
İlk deploy'da migration ile yüklenir; system panelden ekleme/çıkarma mümkün.
System ekran taslakları
| ⋮⋮ | İkon | Ad | Slug | Satıcı | Sıra | Görünürlük | Durum | |
|---|---|---|---|---|---|---|---|---|
| ⋮⋮ | 🍕 | Pizza | pizza | 842 | 1 | Keşif Başvuru | Aktif | Düzenle |
| ⋮⋮ | 🥙 | Döner | doner | 1.204 | 2 | Keşif Başvuru Detay | Aktif | Düzenle |
| ⋮⋮ | 🍰 | Tatlı | tatli | 318 | 8 | Keşif | Pasif | Düzenle |
Önizleme — Müşteri app
Bekleyen: Lezzet Durağı — Kadıköy
Başvuru: 28.06.2026 · Sahip: ahmet@example.com
Onay sırasında PUT /sellers/:id/cuisines ile en az bir mutfak atanması zorunludur.
Menü kategorisi (MenuCategory) yönetimi satıcı panelinde kalır — system ekranına eklenmez (rakiplerle uyumlu).
7 · Teknoloji Önerisi
Karar özeti
Fan Yemek backend'i TypeScript + NestJS (modüler monolit) ile yazılmalıdır; kalıcı katman için TypeORM kullanılır. Ekip profili (1 Kotlin / 5–6 Nest.js) ve Faz 1 çıkış hızı bu seçimi destekler.
Ekip profili
1 kişi — Kotlin / Spring
Domain port, entegrasyon modülleri (tahsilat-app, kurye connector), mimari denetim ve code review.
5–6 kişi — JS / Nest.js
Faz 1 çekirdek modüllerin paralel geliştirilmesi: auth, katalog, sepet, sipariş, satıcı paneli API'leri.
Fan Yemek proje atamaları için bkz. Bölüm 9 · Proje Ekibi.
Neden NestJS?
- Ekip hızı: Faz 1 kapsamı geniş; çoğunluk Nest.js deneyimli — paralel modül geliştirme mümkün.
- Mimari uyum: NestJS modül/DI yapısı Spring'e benzer; modüler monolit disiplini kolay kurulur.
- Proje profili: Marketplace backend I/O ağırlıklı (DB, Redis, queue, harici API); Node.js yeterince güçlü.
- Ekosistem: Redis, BullMQ/Kafka, Elasticsearch, JWT/RBAC, OpenAPI — olgun entegrasyonlar mevcut.
- ORM:
@nestjs/typeorm+ TypeORM — entity/repository kalıbı Spring JPA'ya yakın; migration ve PostGIS desteği net.
TypeORM (NestJS ile)
- Entity tanımı: bu referanstaki modeller (
DeliveryShipment,CourierProvidervb.) TypeORM entity olarak domain modüllerine map edilir. - Migration:
typeorm migration:generate/run— şema değişiklikleri versiyonlu ve CI'da uygulanır. - PostGIS:
geography(Point)/ raw query ile satıcı ve adres mesafe sorguları. - Transaction: sipariş + ödeme + outbox yazımları
QueryRunnerveya@Transactional()ile atomik tutulur.
Kotlin + Spring ne zaman mantıklı olurdu?
| Koşul | Durum |
|---|---|
| Şirket standardı zorunlu JVM | Belirtilmedi |
| resto-maxi-app kodu doğrudan paylaşılacak | Hayır — ayrı servis kararı |
| Ekip çoğunlukla JVM deneyimli | Hayır — çoğunluk Nest.js |
| CPU-yoğun batch/analytics ağırlıklı | Hayır — event-driven marketplace |
resto-maxi-app (Spring Boot + Kotlin) domain ve pattern kaynağı olarak kullanılır; birebir kod paylaşımı şart değildir.
Önerilen stack
Runtime: Node.js LTS + TypeScript
Framework: NestJS (modüler monolit)
ORM: TypeORM (@nestjs/typeorm)
DB: PostgreSQL + PostGIS
Cache: Redis (ioredis)
Queue: BullMQ / RabbitMQ (Kafka — trafik büyüdükçe)
Search: Elasticsearch
Auth: Telefon OTP (WhatsApp/MonoChat + Twilio SMS), JWT + refresh rotation, RBAC
API: REST /v1 (bu referanstaki sözleşme)
Observability: OpenTelemetry + structured logging
Deploy: Docker + CI/CD
Faz 1 modül önerisi
Kaçınılması gerekenler
- Hibrit stack (bazı servisler Nest, bazıları Spring) — Faz 1'de operasyonel yük iki katına çıkar.
- Kotlin seçip ekibi hızla Kotlin'e çevirmek — 5–6 kişinin ramp-up süresi Faz 1'i geciktirir.
- Erken microservice — önce modüler monolit; trafik kanıtlandıkça modülleri ayırın.
| Seçenek | Faz 1 hızı | Ekip uyumu | resto-maxi reuse |
|---|---|---|---|
| NestJS + TS + TypeORM (önerilen) | Yüksek | Çok iyi | Referans/port |
| Kotlin + Spring | Düşük (darboğaz) | Zayıf | Doğrudan reuse |
| Hibrit | Orta | Kötü | Kısmi |
8 · Kurye Connector Mimarisi
KuryeMaxi ve benzer sağlayıcılarla entegrasyon adapter pattern ile yürütülür. Fan Yemek'in sorumluluğu ince bir sözleşmedir: teslimat talebi gönder, webhook ile durum al, müşteriye normalize tracking sun.
| CourierProvider metodu | Yön | Açıklama |
|---|---|---|
| createDelivery(orderSnapshot) | Outbound | Sipariş ready → sağlayıcıya teslimat talebi; externalRef + trackingUrl döner |
| cancelDelivery(externalRef) | Outbound | Teslimat iptali (sipariş iptalinde) |
| getTracking(externalRef) | Outbound (opsiyonel) | Sağlayıcıdan anlık durum/ETA çekme (webhook yedek) |
| handleWebhook(payload, signature) | Inbound | Sağlayıcı callback → normalize statü → Order + DeliveryShipment güncelle |
| Sağlayıcı statüsü (örnek) | Fan Yemek normalize |
|---|---|
| VALIDATED / ASSIGNED (MuditaKurye) | assigned |
| PREPARED / picked up | picked_up |
| ON_DELIVERY / status:1 (KuzeyPOS) | on_the_way |
| DELIVERED / status:2 | delivered |
| FAILED / CANCELLED | failed | cancelled |
| Sağlayıcı | Outbound | Inbound webhook | Public API |
|---|---|---|---|
| KuryeMaxi | Partner API (placeholder) | Durum callback | Yok — partner anlaşması gerekir |
| MuditaKurye | POST /webhook/third-party/order | VALIDATED → DELIVERED | Dokümantasyon |
| KuzeyPOS / PaketSefim | Sipariş payload push | status: 1 (yolda), 2 (teslim) | Webhook dok. |
| Paketiniz Entegre | REST + webhook | durum kodu + teslimat tarihi | Özel dok. (kurumsal) |
Yeni sağlayıcı eklemek: (1) CourierProviderAdapter implement et, (2) system'den CourierProvider kaydı + credentials, (3) satıcı/bölge eşlemesi yap, (4) webhook URL'ini sağlayıcıya ver (POST /webhooks/courier/:provider).
9 · Proje Ekibi
Frontend
Yasin Karadeniz
Backend
Recep Özen
Backend
Nuh Yılmaz Şentürk
Backend
Furkan Öğütçü
Backend
Kadir Belen
Backend10 · Billing & Ledger Kararı
Billing revizyonu ertelendi
Aşağıdaki ledger/overdraft yaklaşımı, ayrı bir ürün dokümanında (FanYemek Ödeme Mekanizması — snapshot-tabanlı gelir paylaşımı) revize edilmektedir. Karar netleşene kadar bu bölüm güncel değildir; finans/billing implementasyonu bekletilmiştir. Aktör yeniden adlandırma ve tenant paneli revizyonu bu ertelemeden bağımsız uygulanmıştır.
Billing üç katmandan oluşur: tahsilat-app (kart/APM tahsilat + webhook), ledger (double-entry muhasebe — komisyon split, hakediş birikimi, iade kaydı) ve finans ops (Payout, Refund domain entity'leri). Medici (MongoDB) bu stack'e uymaz; PostgreSQL + NestJS + TypeORM için birincil aday @vynelix/nestjs-accountant değerlendirilecektir.
Overdraft (eksi bakiye) — zorunlu
- Hesap sahipleri (öncelikle müşteri / User cüzdanı) konfigüre edilmiş limit dahilinde eksi bakiye kullanabilmelidir.
- Hesap bazlı politika: overdraft hesap başına açılır/kapatılır; varsayılan kapalı, müşteri cüzdanları için açılabilir.
- Limit:
overdraftLimitMinor(TRY kuruş) tavan; aşımda işlem reddedilir. - Ledger: overdraft, LIABILITY hesabının negatife inmesine izin verir; debit = credit bütünlüğü korunur.
nestjs-accountantAccount.allowNegativesunar; limit uygulaması uygulama katmanında (OverdraftGuardService) veya custom ledger entity alanında.- Faz 3 MVP dışı: faiz/tahakkuk, otomatik tahsilat döngüsü (altyapı bunları engellememeli).
Birincil — nestjs-accountant POC (Faz 3 öncesi)
- Paket:
@vynelix/nestjs-accountant(README'dekinestjs-accountantadı yanlış) - Stack: NestJS 11 + TypeORM + PostgreSQL (Bölüm 7 ile uyumlu)
- Kapsam: yalnızca ledger çekirdeği; tahsilat-app ve
Payoutayrı kalır tenantId→Tenant.idreferenceType: USER→ müşteri cüzdanı (LIABILITY, overdraft açık)referenceType: RESTAURANT→ satıcı hakediş hesabıreferenceType: ORDER+ capture → marketplace split transactionidempotencyKey→Payment.externalRef
Bilinen riskler:
createPendingTransaction→POSTEDgerçek capture yapmıyorisFrozenenforce edilmiyor- v0.0.1, test suite yok, düşük adoption
POC geçerse Faz 3'te billing/ledger modülüne entegre edilir.
Son seçenek — Custom TypeORM Ledger
- nestjs-accountant go/no-go başarısız olursa devreye girer
- 4 entity:
LedgerAccount,LedgerBalance,LedgerTransaction,LedgerEntry LedgerAccount.allowNegative+overdraftLimitMinorfirst-class- Pessimistic locking + idempotency (Medici / nestjs-accountant pattern referans)
Payment,Payout,Refunddomain entity'leri korunur- Opsiyonel ilham: pgledger (SQL constraint'ler)
POC go/no-go kriterleri (kod POC sonraki adım)
| Senaryo | Beklenen | Kritik |
|---|---|---|
| Marketplace split | Debit = Credit; satıcı + platform fee + KDV doğru | Evet |
| Idempotency | Aynı idempotencyKey duplicate oluşturmaz |
Evet |
| Reversal (tam iade) | Mirror transaction; bakiye geri alınır | Evet |
| Multi-tenant izolasyon | Tenant A bakiyesi Tenant B'den görünmez | Evet |
| Concurrent capture | Paralel siparişlerde race condition yok | Evet |
| Auth → capture (pending) | Capture'da bakiye güncellenir — bilinen gap; patch veya Kart B | Evet |
| Overdraft — limit içi debit | allowNegative: true hesap negatife iner |
Evet |
| Overdraft — limit aşımı | Limit üstü işlem red; bakiye değişmez | Evet |
| Overdraft — kapalı hesap | allowNegative: false → negatif red |
Evet |
| Checkout overdraft ile sipariş | Eksi cüzdan → split transaction dengeli post | Evet |
| Partial refund | Kısmi tutar reversal | Orta (Faz 3) |
| Payout dönemsel toplama | Satıcı LIABILITY bakiyesi → Payout |
Orta |
Go: tüm kritik satırlar geçer (overdraft dahil); pending/capture patch kabul edilebilir. No-go: bütünlük, tenant izolasyonu, overdraft limit veya concurrency fail → custom ledger (Kart B).
Veri Modelleri
Domain sekmeleri, alan detayları, ilişkiler ve ER diyagramı
Entity-Relationship Diyagramı
Modeller arası temel ilişkiler — FK alanlarına tıklayarak modele atlayabilirsiniz
erDiagram
User ||--o{ Address : has
User ||--o| UserPreferences : has
User ||--o{ PaymentMethod : has
User ||--o{ PushToken : has
User ||--o{ Cart : owns
User ||--o{ Order : places
User ||--o{ Review : writes
User ||--o{ Notification : receives
SystemUser ||--o{ AuditLog : creates
SystemUser ||--o{ Refund : processes
Seller ||--o{ SellerStaff : employs
Seller ||--o{ WorkingHoursDay : has
WorkingHoursDay ||--o{ WorkingHoursInterval : contains
Seller ||--o{ MenuDefinition : owns
Seller ||--o{ Order : receives
Seller ||--o{ Payout : receives
Seller }o--o{ CuisineCategory : tagged
Tenant ||--o{ TenantUser : has
Tenant ||--o{ SellerTenant : hosts
Seller ||--o{ SellerTenant : applies
Tenant ||--o{ Order : scopes
Tenant ||--o{ Cart : scopes
Tenant ||--o{ Payout : scopes
MenuDefinition ||--o{ MenuDefinitionPublication : published_via
Tenant ||--o{ MenuDefinitionPublication : receives
MenuDefinition ||--o{ MenuCategory : contains
MenuCategory ||--o{ MenuItem : lists
MenuType ||--o{ MenuItem : classifies
MenuItem ||--o{ MenuItemOptionGroup : has
MenuItemOptionGroup ||--o{ MenuItemOption : contains
Cart ||--o{ CartItem : contains
Cart }o--|| Seller : from
CartItem }o--|| MenuItem : references
Order ||--o{ OrderItem : contains
Order ||--o| Review : has
Order ||--o| Payment : paid_by
Order ||--o| DeliveryShipment : fulfilled_by
Order }o--|| Address : delivered_to
DeliveryShipment }o--|| CourierProvider : via
CourierProvider ||--o{ DeliveryShipment : handles
DeliveryShipment ||--o{ DeliveryStatusEvent : logs
Seller ||--o| SellerCourierConfig : courier_config
SellerCourierConfig }o--|| CourierProvider : uses
Payment ||--o{ Refund : may_have
Coupon }o--o{ Cart : applied_to
User ||--o{ Favorite : saves
Favorite }o--o| Seller : refs
Favorite }o--o| MenuItem : refs
User ||--o{ UserCoupon : wallet
UserCoupon }o--|| Coupon : of
Campaign }o--o| Coupon : promotes
User ||--o{ SupportTicket : opens
SupportTicket }o--o| Order : about
Tenant ||--|| TenantPaymentConfig : payment_config
Tenant ||--o{ Payment : scopes
FulfillmentType ||--o{ SellerFulfillmentSetting : offered
PaymentMethodType ||--o{ SellerPaymentSetting : accepted
MealCardProvider ||--o{ SellerPaymentSetting : meal_card
Seller ||--o{ SellerFulfillmentSetting : configures
Seller ||--o{ SellerPaymentSetting : accepts
SellerFulfillmentSetting }o--o| Tenant : tenant_override
SellerPaymentSetting }o--o| Tenant : tenant_override
Order }o--|| FulfillmentType : fulfillment
Order }o--|| PaymentMethodType : payment_type
Province ||--o{ District : contains
District ||--o{ Neighborhood : contains
Province ||--o{ Neighborhood : denormalized
Neighborhood ||--o{ NeighborhoodCell : divided_into
Seller ||--o{ DeliveryZone : serves
DeliveryZone ||--o{ DeliveryZoneCell : covers
DeliveryZone }o--o| Tenant : tenant_override
DeliveryZoneCell }o--o| Neighborhood : sourced_from
Çalışma Zamanı Akışları & Davranış
Sistemin "nasıl çalıştığı": sipariş yaşam döngüsü, ödeme, kurye dağıtımı ve realtime/event mimarisi
1 · Sipariş Statü Makinesi
Siparişin tüm yaşam döngüsü tek bir durum makinesi ile yönetilir. Geçişler yalnızca tanımlı yönlerde ve yetkili aktör tarafından yapılabilir; geçersiz geçiş 409 INVALID_STATUS_TRANSITION döndürür.
| Geçiş | Tetikleyen | Ön koşul / Not |
|---|---|---|
| pending → confirmed | seller (owner · temsilci) | Online ödemede authorize edilmiş olmalı; kabul ile capture. COD/banka havalesinde doğrudan onay. |
| pending → rejected | seller · system | Satıcı reddi veya zaman aşımı; authorize iptali (void) |
| confirmed → preparing | seller | Hazırlık başladı |
| preparing → ready | seller | Paket hazır; pickup dışında dispatch tetiklenir |
| ready → delivered | seller · customer | Yalnızca pickup — müşteri aldı / satıcı teslim etti; kurye yok |
| ready → picked_up | connector · webhook | delivery_to_address veya tribune_delivery; dispatch sonrası webhook |
| picked_up → on_the_way | connector · webhook | Sağlayıcı ON_DELIVERY / status:1; trackingUrl müşteriye açılır |
| on_the_way → delivered | connector · webhook | Sağlayıcı DELIVERED / status:2; payout'a sayılır |
| * → cancelled | customer · system | Yalnızca pending/confirmed aşamasında müşteri; sonrası system + iade |
Terminal durumlar: delivered, cancelled, rejected. Her geçiş order.status_changed eventi yayar.
2 · Ödeme Akışı (tahsilat-app)
Online ödemeler (card_online, meal_card_online) tenant'ın TenantPaymentConfig.tahsilatMerchantRef profili üzerinden authorize → capture modeliyle yürür. Kapıda nakit/yemek kartı ve banka havalesi tahsilat-app authorize akışına girmez.
Ödeme türüne göre dallanma
- card_online / meal_card_online: authorize (checkout) → capture (satıcı kabul) → komisyon
SellerTenant.commissionRateile tenant kapsamında hesaplanır; capture sonrası split transaction ledger'a yazılır (Bölüm 10). - cash_on_delivery / meal_card_on_delivery: sipariş
isPrepaid: false; teslimatta/satıcıda tahsil; komisyon yine tenant komisyon oranından. - bank_transfer: tenant IBAN (
TenantPaymentConfig.bankTransferIban) gösterilir; siparişpending_payment— system/satıcı manuel onay (MVP). - meal_card_online: tahsilat-app APM (Pluxee/Multinet vb.); tenant config'de aktif provider + satıcı üye iş yeri metadata gerekir.
Kritik kurallar
- Tenant scope: tüm tahsilat-app çağrıları aktif tenant'ın merchant ref'i ile yapılır; cross-tenant yönlendirme yasak.
- Idempotency: her ödeme isteği sipariş bazlı
Idempotency-Keyile gönderilir; tekrar denemede çift çekim olmaz (Payment.externalRef). - Başarısız authorize: sipariş
402ile reddedilir, sipariş kaydı oluşmaz veyapayment_failedişaretlenir. - Capture edilmiş ama reddedildi/iptal: sistem otomatik
Refundbaşlatır; tahsilat-app webhook'u ilerefundedteyit edilir. - PCI sınırı: kart verisi yalnızca tahsilat-app'te; Fan Yemek sadece token + işlem referansı tutar.
- Komisyon:
SellerTenant.commissionRate→Tenant.defaultCommissionRate→SystemConfig.platformCommissionDefault.
3 · Kurye Dispatch (Connector)
Sipariş ready olunca teslimat CourierConnector üzerinden dış sağlayıcıya iletilir. Satıcı konfigürasyonundaki CourierProvider seçilir; durum güncellemeleri inbound webhook ile alınır.
Akış notları
- Otomatik dispatch: yalnızca
delivery_to_addressvetribune_delivery— siparişreadyolunca connectorcreateDelivery(orderSnapshot)çağırır (pickuphariç). - Manuel tetik: satıcı panelden
POST /orders/:id/dispatchile yeniden gönderim veya gecikmeli dispatch. - Inbound webhook: sağlayıcı
POST /webhooks/courier/:providerile durum bildirir; HMAC doğrulanır, statü normalize edilir. - Müşteri tracking:
trackingUrlvarsa proxy/passthrough; yoksa normalize statü + ETA. - Geri dönüş:
delivery.failedveya timeout'ta satıcı/operasyona eskalasyon.
4 · Realtime & Event Mimarisi
Domain olayları message broker (Kafka/RabbitMQ) üzerinden yayılır; istemcilere anlık yansıma WebSocket/SSE ile yapılır. Yazma ile event yayımı outbox pattern ile atomik tutulur, consumer'lar idempotenttir.
| Event | Yayınlayan | Tüketen / Kanal | Payload özeti |
|---|---|---|---|
| order.created | Sipariş servisi | Satıcı (SSE), bildirim, arama | orderId, sellerId, items, total |
| order.status_changed | Sipariş servisi | Müşteri (WS), satıcı, bildirim | orderId, from, to, at |
| payment.authorized / captured / refunded | Ödeme servisi | Sipariş, finans, bildirim | orderId, paymentId, amount, status |
| delivery.requested | Connector servisi | Satıcı, audit | orderId, providerId, externalRef |
| delivery.status_changed | Connector (webhook handler) | Müşteri (WS), satıcı, bildirim | orderId, from, to, trackingUrl |
| delivery.failed | Connector servisi | Satıcı, operasyon, bildirim | orderId, providerId, reason |
| notification.broadcast | System | Push/SMS/mail kuyruğu | target, title, body |
5 · Teslimat & Ödeme Seçenekleri (Checkout)
Satıcılar system yönetimli katalogdan (FulfillmentType, PaymentMethodType) kendine uygun seçenekleri işaretler. Checkout'ta görünen liste: TenantPaymentConfig ∩ SellerPaymentSetting (tenant override varsa öncelikli).
Sektör referansı (kısa)
- Trendyol Yemek: online kart + Pluxee/Multinet/Edenred/Setcard (üye satıcılarda); kapıda nakit/yemek kartı geçerli satıcılarda.
- Getir Yemek: platform kuryesinde dijital ödeme; kapıda ödeme yalnızca Satıcı Getirsin modelinde.
- Yemeksepeti: Gel Al + adrese teslim; ödeme checkout'ta teslimat türüne göre filtrelenir.
- Fan Yemek farkı: stadyum senaryosunda
tribune_delivery(engelli tribününe teslim, tribün no).
| FulfillmentType | İzin verilen ödemeler | Kurye dispatch | Checkout alanları |
|---|---|---|---|
| delivery_to_address | card_online, meal_card_online, cash_on_delivery*, meal_card_on_delivery*, bank_transfer | Evet | addressId zorunlu |
| pickup | card_online, meal_card_online, cash_on_delivery, bank_transfer | Hayır | Adres yok |
| tribune_delivery | card_online, meal_card_online, cash_on_delivery*, meal_card_on_delivery* | Evet (tribün noktası) | tribuneNumber, block, accessNote |
* Kapıda ödemeler: satıcı requiresOwnCourier=true veya kurye sağlayıcı COD destekliyorsa. Tenant'ta kapalı ödeme türü satıcı seçse bile checkout'ta görünmez.
6 · Çalışma Saatleri (Haftalık Program)
Satıcı genel ayar olarak haftalık program tutar (WorkingHoursDay + WorkingHoursInterval). Günde birden fazla dilim desteklenir (örn. 08:00–12:00, 13:00–22:00). Gün: ISO 1=Pazartesi … 7=Pazar.
- Program vs.
isOpen: program haftalık şablon;isOpen=falsemanuel kapama (tatil, acil). - Sipariş kabulü:
isOpen === trueVEopenMinutes ≤ nowMinutes ≤ closeMinutes(TZ: Europe/Istanbul). - Gel Al / tribün teslimat: aynı haftalık program geçerli; fulfillment tipine göre ek kısıt yok (MVP).
- İleri tarihli sipariş (
scheduledAt): hedef zaman ilgili günün interval'larından birine düşmeli. - Sayısal alanlar:
openMinutes/closeMinutessunucuda HH:mm'den türetilir; interval'da denormalizeday/sellerIdarama için. - PUT: atomik replace — tüm hafta bir seferde; max 4 interval/gün.
| Kural | 422 koşulu |
|---|---|
| Gün numarası | day ∈ 1..7, schedule'da tekrar yok |
| Kapalı gün | isClosed:true → intervals[] boş |
| Açık gün | isClosed:false → en az 1 interval |
| Dilim sırası | openTime < closeTime; aynı günde çakışma yok |
| Sayısal alan | PUT'ta openMinutes/closeMinutes yok sayılır |
7 · Hizmet Bölgesi & Min. Sepet (H3)
Marketplace teslimatı iki katmanla yönetilir: referans coğrafya (system seed: İl → İlçe → Mahalle; hexagon havuzu yalnız mahallede) ve satıcı teslimat bölgesi (DeliveryZone + DeliveryZoneCell). Runtime lookup saf H3: müşteri lat/lng → hücre → eşleşen bölge → minOrderAmount / deliveryFee / ETA.
Sektör referansı
- Getir Yemek: çoklu bölge, bölge bazlı min sepet + teslimat süresi, haritada H3 altıgen grid.
- Trendyol Yemek: "Alan Seç", "Mahalle Doldur", "Mahalle Ayarı" — bölge başına farklı sepet tutarı (ör. 300–2000 TL).
- Fan Yemek:
Seller.minOrder/SystemConfig.minOrderAmountyalnız fallback; asıl min sepetDeliveryZone.minOrderAmount.
| Tablo | boundary | H3 hücre |
|---|---|---|
| Province | Normalde null; opsiyonel | — |
| District | Normalde null; opsiyonel | — |
| Neighborhood | Normalde zorunlu (GeoJSON) | NeighborhoodCell (persist) |
| DeliveryZone | — | DeliveryZoneCell ⊆ NeighborhoodCell |
- H3 res 9 (~0.10 km²/hücre) —
SystemConfig.deliveryZoneH3Resolutiontüm sistemde sabit. - Persist: H3 deterministik; mahalle kaydında bir kez
polygonToCells→NeighborhoodCell(panel her açılışında yeniden hesaplanmaz). - Subset garantisi: satıcı seçimi ∩ mahalle havuzu; sınır dışı hücre →
422. - Yarıçap:
maxServiceRadiusKmDefault(~15) +Seller.maxServiceRadiusKm; dışı backend'de gönderilmez (hardcut). - SQL: FK zinciri + normalize hücre tablosu +
h3_indexbtree index; il/ilçeON DELETE RESTRICT.
Örnek veri
{
"province": { "id": "il_55", "name": "Samsun", "code": "55", "boundary": null },
"district": { "id": "ilce_5502", "provinceId": "il_55", "name": "Atakum", "boundary": null },
"neighborhood": {
"id": "mah_mevlana", "districtId": "ilce_5502", "provinceId": "il_55",
"name": "Mevlana", "pathString": "/il_55/ilce_5502/mah_mevlana/",
"h3Resolution": 9, "cellCount": 98
},
"neighborhoodCell": { "neighborhoodId": "mah_mevlana", "h3Index": "891ec1a4d4bffff" },
"deliveryZone": { "id": "z1", "sellerId": "r_123", "name": "Bölge 1", "minOrderAmount": 350, "etaMinMinutes": 15, "etaMaxMinutes": 25 },
"deliveryZoneCell": { "zoneId": "z1", "h3Index": "891ec1a4d4bffff", "neighborhoodId": "mah_mevlana" }
}
Sorgu örnekleri
-- 1) Nokta → hücre → satıcıın uygulanan bölgesi (lookup, saf H3)
-- h3 = latLngToCell(lat, lng, 9)
SELECT z.* FROM delivery_zone_cell c
JOIN delivery_zone z ON z.id = c.zone_id
WHERE c.h3_index = :h3 AND z.seller_id = :sellerId AND z.is_active
ORDER BY z.priority LIMIT 1;
-- 2) "Mahalle Doldur"
INSERT INTO delivery_zone_cell (zone_id, h3_index, neighborhood_id)
SELECT :zoneId, nc.h3_index, nc.neighborhood_id
FROM neighborhood_cell nc WHERE nc.neighborhood_id = :neighborhoodId;
-- 2b) "İlçe Doldur" (FK join)
INSERT INTO delivery_zone_cell (zone_id, h3_index, neighborhood_id)
SELECT :zoneId, nc.h3_index, nc.neighborhood_id
FROM neighborhood_cell nc
JOIN neighborhood n ON n.id = nc.neighborhood_id
WHERE n.district_id = :districtId AND n.is_active;
-- 3) Yarıçap-sınırlı mahalleler (hardcut) + X/Y
-- haversine(centroid) <= maxServiceRadiusKm
Tasarım Kapsam Kontrolü (Figma)
SAMYEMEK müşteri tasarımı ile API ağacının karşılaştırması
Tasarımdan eklenen eksikler
SAMYEMEK müşteri tasarımındaki ekranlar API ağacıyla karşılaştırıldı. Çoğu ekran (hesap, adres, ödeme yöntemleri, sepet, checkout, sipariş, arama, satıcı detay, bildirim) zaten karşılanıyordu. Tasarımda olup eksik olan ve Customer API'ye eklenen özellikler:
- Favoriler — satıcı + ürün favorileri (
/favorites); "Favorilerim" ekranı. - İndirim Kuponlarım — müşteri kupon cüzdanı + kod ile ekleme (
/coupons,/coupons/claim). - Kampanyalar — bottom-nav ana sekmesi (
/campaigns). - Yardım & Destek — iletişim, SSS, destek talebi (
/support/*); "Yardım Merkezi" ekranı. - Tekrar sipariş ver — favoriler/geçmişten reorder (
/orders/:id/reorder).
Yeni modeller: Favorite, UserCoupon, Campaign, SupportTicket — Veri Modelleri ve ER diyagramına işlendi.
Kapsam dışı: Samsunspor modülü
Tasarımda bottom-nav'da merkezi bir "Samsunspor" sekmesi ve Puan Tablosu ekranı var (lig sıralaması, fikstür, takım durumu). Bu, marketplace çekirdeğinin dışında bir içerik modülüdür ve büyük olasılıkla dış spor-veri sağlayıcısı entegrasyonu veya bir CMS gerektirir. Şimdilik kapsam dışı kabul edildi; ileride ayrı bir modül (ör. /v1/content/*) olarak ele alınması önerilir.
API Referansı
Aktör seçip endpoint detaylarını (parametre, rol, yanıt) inceleyin