Geliştirici / APIMarketplace API

Marketplace API

Marketplace, bir tenant’ın tek tek satın alabileceği ürünlerin kataloğudur. Ücretsiz çekirdek her tenant’a bir yetki tabanı verir; katalog ürünleri bu tabanı artırır (ek şube, e-Fatura entegrasyonu, gelişmiş raporlar, AI kontörü gibi). Plan, tier ya da paket yoktur; ücretli her yetenek yıllık lisans (license_annual) ön koşuluyla satılır.

Bir ürün satın alındığında bir TenantAddOn satırı oluşur ve entitlement projektörü o satırın grants haritasını tenant’ın mevcut hak setine katar (fold). Kombinleme kuralı anahtar önekine göre değişir:

ÖnekTipKombinlemeÖrnek
limit.*sayıSUM (toplama)Ücretsiz taban limit.maxBranches: 1 + 2× extra_branch (+1 her biri) = 3
feature.*booleanORHerhangi bir kaynak feature.advancedReports: true veriyorsa → açık
integration.*string[]UNION (birleşim)fiscal_efatura + fiscal_huginintegration.fiscal: ["efatura", "hugin"]

limit.* için -1 “sınırsız” sentinel’idir: herhangi bir kaynak -1 verirse, sonuç sınırsız olur ve toplama durur (sınırsız bir cap’e kapasite eklemek no-op’tur).

Tüm uçlar /api global ön eki altındadır. Fiyatlar TRY cinsindendir ve kataloğda kuruş (priceCents) olarak tutulur: 490000 = ₺4.900,00. Tutarlar KDV dahil (gross) gösterilir. Taban URL, kimlik realm’leri, hata zarfı ve rate-limit katmanları için API Temelleri sayfasına bakın.

⚠️

Tenant tarafında “bedava grant” veren bir uç yoktur. Eski POST /v1/marketplace/addons/purchase ucu kaldırıldı (deep-review C2): yalnızca @Roles(ADMIN) ile korunuyordu ve purchase()’ı paymentRef olmadan çağırıyordu — yani herhangi bir restoran sahibi curl ile ücretli bir ürünü bedavaya açabiliyordu. Artık ücretli checkout akışı zorunludur; operatör bedavaları (comp) yalnızca süperadmin yüzeyinden verilir.

Uçlar

MetotYolKimlikRollerNotlar
GET/v1/marketplace/addonsPublicKatalog; yalnızca published satırlar
GET/v1/marketplace/addons/availablePersonel JWTADMIN, MANAGERKatalog + includedInPlan işareti (tenant’ın hak setine göre)
GET/v1/marketplace/addons/minePersonel JWTADMIN, MANAGERTenant’ın elindeki ürünler
POST/v1/checkout/intentPersonel JWTADMIN, MANAGERKarma-sepet checkout intent (tek satın alma yolu)
DELETE/v1/marketplace/addons/:idPersonel JWTADMINTenantAddOn iptali (dönem-sonu veya hemen)

Bunlar tenant-seviyesi uçlardır: X-Branch-Id beklemezler. Checkout intent ve iptal ucu para/hak hareketi yaptığı için buna göre korunur.

Vitrini tek çağrıda çizmek için GET /v1/me/licensing daha uygundur: sahip olunanları, kontör bakiyelerini, her yetki anahtarı için bu tenant’a bugün fiyatlanmış teklifi ve her ürün için satın alınabilirlik kararını birlikte döner. Bkz. Lisans & Ödeme API.

Kataloğu listele

Katalog herkese açıktır (landing sitesinde de görünür); kimlik doğrulama gerektirmez. Yalnızca published durumundaki ürünler döner ve her satır UI için kırpılmıştır.

# Tüm published ürünler
curl https://hummytummy.com/api/v1/marketplace/addons
 
# kind ile filtrele: license | module | integration | capacity | credit | service
curl "https://hummytummy.com/api/v1/marketplace/addons?kind=integration"

Yanıt:

[
  {
    "code": "module_inventory",
    "name": "Stok & Maliyet Yönetimi",
    "description": "Reçete, stok sayımı, satın alma siparişi, fire takibi, tedarikçi yönetimi ve şubeler arası transfer.",
    "kind": "module",
    "billing": "annual",
    "priceCents": 390000,
    "currency": "TRY",
    "deps": [],
    "requiresLicense": true,
    "creditKind": null,
    "creditUnits": null,
    "maxQuantity": null,
    "sortOrder": 11,
    "i18n": { "tr": { "name": "…", "description": "…" }, "en": { "…": "…" } }
  }
]

grants alanı public katalogda döndürülmez — yalnızca süperadmin yüzeyi ve katalog kaynak dosyası hak haritasını taşır. Katalog UI için kırpılmıştır; priceCents/currency fiyat için kaynak gerçektir. Yıllık satırların bu tenant için bugünkü orantılı fiyatı GET /v1/me/licensing içindeki offers[...].proratedCents alanından okunur.

Katalog özeti

Tüm sellable ürünler tek bakışta. Açtığı sütunu burada yalnızca referans için gösterilir; public katalog payload’ının parçası değildir. Ayrıntılar için Yetki Matrisi.

KodTürFaturaFiyatAçtığı (grant)LisansBağımlılık
license_annuallicenseyıllık₺4.900 (490000)feature.license, feature.prioritySupport, integration.fiscal += efatura
advanced_reportsmoduleyıllık₺1.290 (129000)feature.advancedReports
module_inventorymoduleyıllık₺3.900 (390000)feature.inventoryTracking
module_reservationsmoduleyıllık₺990 (99000)feature.reservationSystem
module_personnelmoduleyıllık₺990 (99000)feature.personnelManagement
module_personnel_card_shiftmoduletek seferlik₺4.000 (400000)feature.cardShiftmodule_personnel
module_ai_studiomoduleyıllık₺1.990 (199000)feature.aiContentGeneration
api_accessmoduleyıllık₺2.490 (249000)feature.apiAccess
module_external_displaymoduleyıllık₺1.990 (199000)feature.externalDisplay
priority_supportmoduleyıllıkfeature.prioritySupportv3.6.7 arşivlendilicense_annual içine alındı
delivery_platformsintegrationyıllık₺2.499 (249900)integration.delivery += yemeksepeti, getir, trendyol_yemek, migros; feature.deliveryIntegration
delivery_yemeksepetiv3.6.8 arşivlendi — delivery_platforms içine alındı
delivery_getirv3.6.8 arşivlendi — delivery_platforms içine alındı
delivery_trendyol_yemekv3.6.8 arşivlendi — delivery_platforms içine alındı
fiscal_efaturaintegrationyıllıkintegration.fiscal += efaturav3.6.7 arşivlendilicense_annual içine alındı
fiscal_huginintegrationyıllık₺2.990 (299000)integration.fiscal += hugin
caller_id_integrationintegrationyıllık₺1.490 (149000)integration.caller += generic
sms_integrationintegrationyıllık₺990 (99000)integration.sms += *
extra_branchcapacityyıllık₺3.990 (399000)limit.maxBranches +1, feature.multiLocation
credit_ai_photo_100credittek seferlik₺690 (69000)— (100 PHOTO kontörü)module_ai_studio
credit_ai_video_20credittek seferlik₺890 (89000)— (20 VIDEO kontörü)module_ai_studio
credit_ai_3d_10credittek seferlik₺790 (79000)— (10 MODEL3D kontörü)module_ai_studio
credit_sms_500credittek seferlik₺490 (49000)— (500 SMS kontörü)sms_integration
onsite_install_fullservicetek seferlik₺7.500 (750000)— (grants: {}, hizmet satırı)
⚠️

onsite_install_full bir hizmet kalemidir: ödenir ve faturalanır ama hiçbir entitlement vermez (grants: {}). Tekrarlayan değil tek seferliktir ve dönem sonu penceresi de almaz (currentPeriodEnd null). Kontör paketleri de aynı şekilde grant taşımaz — bir bakiye açarlar ve TenantAddOn değil CreditLot satırı üretirler.

extra_branch tek satın almada hem limit (SUM) hem feature (OR) grant’i taşır: { "limit.maxBranches": 1, "feature.multiLocation": true }. Çoklu şube arayüzü zaten ücretsizdir; ücretli olan ikinci şubenin kendisidir. Üç delivery_* ürünü de integration.delivery anahtarına yazar ve union edilir; yanlarındaki feature.deliveryIntegration bayrağı OR ile katlanır.

Emekliye ayrılmış kodlar (kds_extra_screen, kds_extra_station, extra_tablet) arşivlendi, silinmedi: MarketplaceAddOn.code yeniden kullanılamaz ve TenantAddOn.addOnId onDelete: Restrict taşır. Üçü de hiçbir kodun okumadığı limit.kdsScreens / limit.kdsStations / limit.tablets anahtarlarını veriyordu; à-la-carte modeli cihaz kapasitesi fiyatlamasını tamamen bıraktı.

Ürün satın al (checkout intent)

Bir ürün satın almanın tek yolu checkout (PayTR) rayıdır. Ücretli bir ürün, ödeme kanıtı (paymentRef) olmadan asla provize edilmez.

Checkout intent başlat

Seçilen ürünleri (artı varsa donanım / hizmet satırları) tek bir karma sepet olarak POST /v1/checkout/intent’e gönderin. Sunucu sepeti yeniden fiyatlandırır (istemci toplamına asla güvenmez), CK-<uuid7> biçiminde bir paymentRef üretir, sepeti bir CheckoutIntent satırına dondurur ve PayTR iframe token’ı + ödeme linki döner.

curl -X POST https://hummytummy.com/api/v1/checkout/intent \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "cart": {
      "items": [
        { "type": "addon", "code": "license_annual", "qty": 1 },
        { "type": "addon", "code": "fiscal_hugin", "qty": 1 }
      ]
    },
    "buyer": {
      "email": "[email protected]",
      "name": "Ali Veli",
      "phone": "+905551112233",
      "address": "..."
    },
    "returnUrl": "https://app.hummytummy.com/checkout/done",
    "acceptedDocumentIds": ["<KVKK>", "<MESAFELI_SATIS>", "<IADE>"]
  }'

PayTR’da öde

Buyer, PayTR hosted iframe’inde ödemesini yapar. Sepetin her satırı buyer’a ayrı bir kalem olarak gösterilir (ör. “e-Fatura (Nilvera) (yıllık)”).

Webhook ile yerleşim (settlement)

PayTR webhook’u yalnızca merchant_oid + total_amount taşır. Dispatcher CK- önekini görüp çağrıyı CheckoutSettlementService’e yönlendirir. Burada CheckoutIntent bulunur, dondurulmuş sepet okunur ve CheckoutService.confirmAndProvision çalışır.

Grant’lar yerleşir

confirmAndProvision, her katalog satırı için TenantMarketplaceService.purchase()’ı ödemeli paymentRef ile ve checkout’un transaction’ı içinde çağırır. Bu da bir TenantAddOn satırı oluşturur (ya da süresi geçmiş bir satırı yerinde yeniden etkinleştirir) ve outbox’a AddOnPurchased event’i yazar. Entitlement projektörü AddOnPurchased’ı tüketir ve ürünün grants haritasını tenant’ın hak setine katar; guard’lar ve UI artık yeni limit/feature/integration’ı görür.

Satın alınabilirlik ve bağımlılıklar (deps)

Her addon satırı, ödeme alınmadan önce satın alınabilirlik kapısından geçer. Reddedilen satır 409 ile döner ve sepetin tamamı durur:

codeAnlamı
LICENSE_REQUIREDÜrün lisans ister; sepette de lisans yok
ADDON_ALREADY_OWNEDBu ürün (ya da lisans) zaten aktif
ADDON_ALREADY_GRANTEDÜrünün verdiği her şey zaten yetki setinde var
ADDON_REQUIRES_DEPENDENCYBağımlı olduğu ürün ne sahiplikte ne sepette
ADDON_LIMIT_REDUNDANTEklediği kapasite zaten sınırsız
ADDON_MAX_QUANTITYKatalog tavanı aşılıyor (extra_branch için 100)

deps dizisindeki her giriş bir katalog ürün kodudur ve satın alma anında aktif bir sahiplik satırıyla ya da aynı sepetteki bir kardeş satırla karşılanmalıdır. Bugün deps taşıyan ürünler dört kontör paketidir (credit_ai_*module_ai_studio, credit_sms_500sms_integration).

⚠️

Eski plan:<AD> biçimi (ör. fiscal_hugin’in plan:PRO bağımlılığı) planlarla birlikte kaldırıldı. Katalog doğrulaması artık plan: önekli her bağımlılığı yazma anında reddediyor: Tenant.currentPlanId her tenant için NULL olduğundan böyle bir dep asla karşılanamaz ve o ürünün her satışını bloklardı.

Çift-satın-alma koruması

Aynı ürünü iki kez alıp hiçbir şey kazanmamak engellenir: aynı (tenantId, addOnId, branchId) için aktif bir satır varsa ikinci satın alma ADDON_ALREADY_OWNED ile reddedilir. Kapasite ürünleri adetle alınır, bu yüzden onlarda tavan maxQuantity’dir. Kontör paketleri tüketilir, bu yüzden hiç engellenmez. Eş zamanlı iki satın alma Serializable transaction ile yakalanır; kaybeden işlem yeniden denenebilir bir 409 alır (kart çift çekilmez).

İdempotensi

PayTR retry’leri agresiftir (200 OK dönse bile gövde “OK”/“FAIL” değilse tekrar dener). Çift provizyona karşı çok katmanlı koruma vardır:

CheckoutIntent.status kontrolü

Intent zaten provisioned/failed ise hiçbir şeye dokunulmaz.

confirmAndProvision bağımsız idempotenttir

(tenantId, paymentRef) üzerinde idempotenttir.

purchase() mevcut satırı döner

Aynı paymentRef için mevcut TenantAddOn satırını, AddOnPurchased event’ini yeniden yazmadan döner.

Mevcut ürünlerim

Tenant’ın elindeki satırları listeler.

curl https://hummytummy.com/api/v1/marketplace/addons/mine \
  -H "Authorization: Bearer $JWT"

Her satır ürün kataloğunu (addOn) ve durum/dönem alanlarını taşır:

[
  {
    "id": "ta_01J...",
    "tenantId": "t_01J...",
    "addOnId": "ao_01J...",
    "branchId": null,
    "quantity": 2,
    "pendingQuantity": null,
    "status": "active",
    "activatedAt": "2026-08-13T10:00:00.000Z",
    "currentPeriodStart": "2026-08-13T00:00:00.000Z",
    "currentPeriodEnd": "2027-03-10T00:00:00.000Z",
    "cancelAtPeriodEnd": false,
    "paymentRef": "CK-018f...",
    "chargedCents": 434300,
    "currency": "TRY",
    "origin": "purchase",
    "addOn": { "code": "extra_branch", "name": "Ek Şube", "...": "..." }
  }
]
AlanAnlamı
statusactive · past_due (ödemesiz dönem, hak hâlâ canlı) · cancelled · expired
quantity / pendingQuantityŞu anki adet / bir sonraki yıl dönümünde uygulanacak adet (kapasite düşüşleri yenileme zamanlıdır)
chargedCentsBu dönem için gerçekten tahsil edilen (orantılı) tutar; comp’larda 0
originpurchase · comp · migration

Yıllık ürünler rolling bir pencere değil, hesabın yıl dönümüne kadar sürer — currentPeriodEnd her satırda aynı tarihtir, böylece tüm hesap tek tarihte tek faturayla yenilenir. Tek seferlik kalemler (onsite_install_full) currentPeriodEnd: null taşır.

Ürün iptali

İptal yalnızca ADMIN’e açıktır (faturalandırma kararı). Varsayılan olarak satır dönem sonunda iptal edilir; hemen geri almak için ?immediate=true geçin.

curl -X DELETE \
  https://hummytummy.com/api/v1/marketplace/addons/$TENANT_ADDON_ID \
  -H "Authorization: Bearer $JWT"

Satır cancelAtPeriodEnd: true olur ve aktif kalır. Verdiği limit/feature/integration hakları, gece taraması (sweeper) / yenileme döngüsü kapanışı satırı cancelled’a çevirene kadar yaşamaya devam eder.

Yalnızca active ve past_due satırlar iptal edilebilir; başka bir durumda istek 400 alır. past_due bir satırda iptal her zaman anında uygulanır — ödenmiş dönemi zaten bitmiştir, dönem sonuna bırakmak grace hakkını canlı tutmaktan başka işe yaramazdı.

⚠️

İki eşzamanlı iptal çağrısı tek bir geçişe yakınsar (status koşullu updateMany). Kaybeden çağrı Cancel raced with another request — refresh and retry hatası alır; AddOnCancelled event’i iki kez yazılmaz.

İlgili

  • Yetki Matrisi — ücretsiz taban, ürün grant’ları, lisans karartması ve ENTITLEMENT_REQUIRED zarfı.
  • Lisans & Ödeme API — checkout rayı, orantılı fiyatlama, yıl dönümü, faturalar ve yenileme.
  • API Temelleri — taban URL, kimlik realm’leri, hata zarfı, rate-limit katmanları.
  • Webhook’lar — giden olayları alın (api_access modülü feature.apiAccess’i açar).
  • Operatör deneyimi (panelde ürünlere göz atma, satın alma ve takip): Marketplace yardımı.