FirsatGO POS Entegrasyonu — HİZMET (Service) Sektörü Developer Dokümanı
Genel Bakış
FirsatGO bir talep ağı (Getir / Trendyol-GO konumu), POS entegratörünüz ise FirsatGO sözleşmesine uyan tarafdır. Hizmet (service) sektörü, platformdaki diğer sektörlerden temelde farklıdır: burada kalemli-sipariş / sepet / fiş kavramı YOKTUR. Akış randevu ve iş-emri (work order) tabanlıdır: müşteri bir sağlayıcıdan iş ister → sağlayıcı teklif verir (veya sabit-fiyatlı katalog kalemi anında book edilir) → kabul → iş emri açılır → randevu/saha/teslimat aşamaları → müşteri kabulü → ödeme.
KRİTİK SEKTÖR-FARKI: Hizmet siparişleri
Orderakışından GEÇMEZ. Kendi state-machine'iservice-engine'dedir. FirsatGO'nun POS'a sipariş gönderdiğiPOST {posBase}/orderswebhook'u ve POS→FG sipariş pull/status yüzeyleri (B) hizmet sektöründe kullanılmaz. Kod tarafında bunun teyidi nettir:integrations/veexternal-api/modüllerinde hiçbir iş-emri/talep referansı yoktur. food/retail kalemli-sipariş modeli hizmette UYGULANMAZ.
Sektör motoru: engine=service. Profiller: field_service (saha), professional_service (profesyonel), hybrid_service (hibrit). İşletmenin sector alanı services olmalıdır (fail-closed doğrulama).
Sipariş Yaşam-Döngüsü (Hizmet State-Machine'i)
Hizmette tek bir "sipariş durumu" yoktur; üç ayrı varlığın durum makineleri zincirleme çalışır: ServiceRequest (talep) → ServiceQuote (teklif) → WorkOrder (iş emri).
1) Talep — ServiceRequest.status
| Durum | Anlam |
|---|---|
open | Müşteri talep oluşturdu (fiyat henüz yok) |
quoted | Sağlayıcı en az bir teklif verdi |
accepted | Teklif kabul edildi → iş emri açıldı |
closed | Talep kapatıldı |
cancelled | Talep iptal edildi |
2) Teklif — ServiceQuote.status
| Durum | Anlam |
|---|---|
pending | Aktif teklif, müşteri yanıtı bekleniyor |
accepted | Müşteri kabul etti → iş emri açıldı |
rejected | Müşteri reddetti |
expired | Geçerlilik süresi (valid_until) doldu |
revised | Sağlayıcı yeni revizyon (revision_no++) verdi, bu teklif geçersizleşti |
3) İş Emri — WorkOrder.status (asıl yürütme makinesi)
| Durum | Anlam | Geçebileceği durumlar |
|---|---|---|
scheduled | İş emri açıldı / randevu planlandı | in_progress, cancelled |
in_progress | İş başladı (sağlayıcı ilerletti) | awaiting_acceptance, cancelled |
awaiting_acceptance | Tamamlandı, müşteri onayı bekleniyor | in_progress (geri), completed, cancelled |
completed | Müşteri kabul etti → ödeme aşamasına hazır (terminal) | — |
cancelled | İptal (terminal). appointment_at $unset → slot serbest kalır | — |
Geçiş aktörleri: scheduled→in_progress→awaiting_acceptance ve cancel sağlayıcı tarafından (PUT /work-orders/:id/transition); awaiting_acceptance→completed müşteri kabulüyle (POST /work-orders/:id/accept).
Para koruması: payment_status unpaid değilse (yani processing/partial/paid) iş emri doğrudan cancelled yapılamaz — önce iade akışı çalışmalıdır.
Ödeme durumu — WorkOrder.payment_status
unpaid | processing (tam-ödeme tahsilatı uçuşta, atomik claim) | partial (bazı milestone'lar ödendi) | paid | refunded.
Sektöre-Özel Sipariş Alanları (yalnız hizmette geçerli)
Hizmette Order payload'u / fulfillment{} / items[] bloğu YOKTUR. Onun yerine iş-emri (work order) ve bağlı teklif alanları geçerlidir.
WorkOrder alanları
| Alan | Tip | Açıklama |
|---|---|---|
quote_id | ObjectId | Kaynak teklif |
request_id | ObjectId | Kaynak talep |
merchant_id | ObjectId | Sağlayıcı işletme |
customer_id | ObjectId | Müşteri |
amount | number (₺) | Kabul edilen teklif tutarı (saf hizmet bedeli) |
status | enum | İş emri durumu (yukarıdaki tablo) |
appointment_at | Date | Randevu zamanı (slot rezervasyonu; dakikaya yuvarlanır) |
status_history[] | {status, actor, at, note} | Durum geçmişi |
before_photos[] / after_photos[] | string[] | İş öncesi/sonrası fotoğraflar |
acceptance | {accepted_at, note} | Müşteri kabulü |
payment_status | enum | unpaid/processing/partial/paid/refunded |
milestones[] | {title, amount, status, due_date, paid_at} | Aşama bazlı ödeme planı (professional/hybrid). status: pending/processing/paid |
deliverables[] | {name, url, note, uploaded_at} | Teslim edilen çıktılar (professional) |
site_visit | {scheduled_at, completed_at, notes, photos[]} | Saha ziyareti (field/hybrid) |
ServiceQuote.line_items[] (food items[]'ın hizmet karşılığı)
| Alan | Tip | Açıklama |
|---|---|---|
description | string | Kalem açıklaması (örn. "İşçilik", "Malzeme") |
quantity | number | Adet |
unit_price | number (₺) | Birim fiyat |
Teklif ayrıca: amount (toplam), notes, valid_until, revision_no, status.
Not:
line_itemsfood/retail kalemli-siparişten farklıdır — bunlar fiş kalemi değil, teklif kalemleridir; POS'a sipariş olarak gönderilmez, FirsatGO içinde teklif round-trip'inde kullanılır.
Örnek iş-emri JSON
{
"_id": "665f0a...",
"quote_id": "665f09...",
"request_id": "665f08...",
"merchant_id": "664c...",
"customer_id": "6612...",
"amount": 2500.00,
"status": "in_progress",
"appointment_at": "2026-07-02T11:00:00.000Z",
"payment_status": "unpaid",
"milestones": [
{ "title": "Keşif + avans", "amount": 1000, "status": "paid", "due_date": "2026-07-01T00:00:00.000Z" },
{ "title": "Teslim", "amount": 1500, "status": "pending", "due_date": "2026-07-10T00:00:00.000Z" }
],
"site_visit": { "scheduled_at": "2026-07-02T11:00:00.000Z", "notes": "2. kat, kombi dairesi" },
"status_history": [
{ "status": "scheduled", "actor": "customer", "at": "2026-06-28T09:00:00.000Z" },
{ "status": "in_progress", "actor": "merchant", "at": "2026-07-02T11:05:00.000Z" }
]
}
Katalog / Ürün
Hizmette "ürün" = ServiceListing (sağlayıcının yayınladığı sunduğu hizmet). food catalog.product şeklinden farklıdır.
| Alan | Tip | Açıklama |
|---|---|---|
merchant_id | ObjectId | Sağlayıcı işletme |
title | string | Hizmet başlığı |
description | string? | Açıklama |
service_type | string? | Hizmet türü (örn. tesisat, sac_kesim) |
pricing_mode | enum | fixed (sabit fiyat → anında booking + ödeme) veya quote (teklif round-trip'i) |
base_price | number? | pricing_mode=fixed için zorunlu (>0) |
duration_min | number? | Tahmini süre (dakika) |
images[] | string[] | Görseller |
is_active | boolean | Yayın durumu |
Uygunluk takvimi — ProviderAvailability (sağlayıcı başına tek kayıt; food'da karşılığı yok):
| Alan | Açıklama |
|---|---|
slot_duration_min | Slot süresi (dk, varsayılan 60, min 5) |
weekly[] | 7 eleman, JS getDay() indeksli (0=Pazar..6=Cumartesi). Her gün: {enabled, open "HH:MM", close "HH:MM"} |
blackout_dates[] | Kapalı günler (YYYY-MM-DD listesi) |
Müsait slotlar = çalışma saatleri − dolu randevular − geçmiş saatler (GET /service-providers/:merchantId/slots?date=YYYY-MM-DD).
Akış Notları (hizmete özgü kurallar)
- Sipariş-webhook'u yerine iş-emri akışı. POS entegratörü için bu sektör, food/retail'den temelden farklıdır: FirsatGO size kalemli-sipariş webhook'u göndermez; ortak sözleşmedeki finansal blok (
original_subtotal,delivery_fee,tax,*_minor,coupon_code,delivery_address,fulfillment{}...) ve durum olayları (order.created,store.open...) hizmette uygulanmaz. Akış FirsatGO içinde randevu/iş-emri state-machine'i ile yürür. - İki giriş yolu:
quotemodu: müşteri talep açar (open) → sağlayıcı teklif verir (quoted/pending) → müşteri kabul eder → iş emri (scheduled) açılır. Teklif revize edilebilir (önceki tekliflerrevised,revision_noartar).fixedmodu: müşteri sabit-fiyatlı katalog kalemini book eder → talep + otomatik-kabul teklif + iş emri (scheduled,amount=base_price) tek seferde oluşur; teklif round-trip'i yoktur.
- Slot rezervasyonu randevulu (
appointment_at) iş emriyle gerçekleşir. Bir sağlayıcıda aynı randevu saatine tek aktif iş emri olabilir (unique partial index; eşzamanlı çift-rezervasyon → DB hatası → 400).appointment_atdakikaya yuvarlanır. İptaldeappointment_atkaldırılır → slot serbest kalır. Slot hesabı sunucu yerel saat dilimine göredir (deploy'daTZpinlenmeli). - Settlement work-order bazlıdır ve saf hizmet bedeli üzerindendir:
amount → komisyon kesilir → satıcı net hakediş. Teslimat ücreti ve vergi YOKTUR (food fişindekidelivery_fee/taxkalemleri burada yer almaz). Ödemecompletedöncesi/sonrası müşteri tarafından yapılır (POST /payments/work-orders/:id/pay),paidolunca settlement işler. - Milestone (aşama) ödemesi (professional/hybrid): iş emri tutarı aşamalara bölünür; her aşama müşteri tarafından ayrı ödenir (
POST /payments/work-orders/:id/milestones/:milestoneId/pay). Aşama tutarları toplamı iş emriamount'unu aşamaz. Her aşama ödemesi kendi settlement'ını üretir; kısmen ödenmiş iş emripayment_status=partial. - İade settlement'ı ters-kayıtla geri alır (tam veya milestone bazlı). Ödemesi alınmış iş emri doğrudan iptal edilemez; önce iade.
- IDOR / tenant-izolasyon: her uç sahiplikle scoped — müşteri yalnız kendi talep/teklif/iş-emrini, sağlayıcı yalnız kendi işletmesinin kayıtlarını görür.
- Profil-bazlı yetenekler:
site_visit(saha ziyareti) → field/hybrid;deliverables(teslim çıktıları) → professional;milestones→ professional/hybrid.
Örnek (gerçekçi hizmet akış payload'ları)
Talep (müşteri → FG):
{
"merchant_id": "664c1f...",
"service_type": "tesisat",
"title": "Kombi bakımı ve petek temizliği",
"description": "3+1 daire, 8 petek; kombi 5 yıllık.",
"budget": 3000,
"address": { "street": "Bağdat Cad. No:12", "district": "Kadıköy", "city": "İstanbul" },
"preferred_date": "2026-07-02T11:00:00.000Z",
"attachments": ["https://cdn.firsatgo.com/req/abc.jpg"]
}
Teklif (sağlayıcı → FG):
{
"amount": 2500,
"line_items": [
{ "description": "Kombi bakım işçiliği", "quantity": 1, "unit_price": 1500 },
{ "description": "Petek temizliği (8 adet)", "quantity": 8, "unit_price": 125 }
],
"notes": "Yedek parça gerekirse ayrıca bilgilendirilecektir.",
"valid_until": "2026-07-01T23:59:00.000Z"
}
Kabul edilip açılan iş emri (FG state):
{
"_id": "665f0a...",
"quote_id": "665f09...",
"request_id": "665f08...",
"merchant_id": "664c1f...",
"customer_id": "6612aa...",
"amount": 2500,
"status": "scheduled",
"appointment_at": "2026-07-02T11:00:00.000Z",
"payment_status": "unpaid",
"site_visit": { "scheduled_at": "2026-07-02T11:00:00.000Z", "notes": "Kapıcıdan anahtar alınacak" },
"status_history": [
{ "status": "scheduled", "actor": "customer", "at": "2026-06-28T10:15:00.000Z" }
]
}
İlgili kaynak dosyalar (as-built doğrulama):
/home/firsatgo/firsatgo-backendv2/src/service-engine/schemas/work-order.schema.ts/home/firsatgo/firsatgo-backendv2/src/service-engine/schemas/service-request.schema.ts/home/firsatgo/firsatgo-backendv2/src/service-engine/schemas/service-quote.schema.ts/home/firsatgo/firsatgo-backendv2/src/service-engine/schemas/service-listing.schema.ts/home/firsatgo/firsatgo-backendv2/src/service-engine/schemas/provider-availability.schema.ts/home/firsatgo/firsatgo-backendv2/src/service-engine/service-engine.service.ts(state-machine, slot/randevu, milestone/deliverable/site_visit)/home/firsatgo/firsatgo-backendv2/src/payments/ledger.service.ts(work-order + milestone settlement; teslimat/vergi yok)/home/firsatgo/firsatgo-backendv2/src/payments/payments.service.ts(payWorkOrder/payMilestone)
Sektör-farkı teyidi: integrations/ ve external-api/ modüllerinde iş-emri/talep/listing referansı yoktur — hizmet siparişleri POS order-webhook yüzeyine hiç dokunmaz.