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:
| Önek | Tip | Kombinleme | Örnek |
|---|---|---|---|
limit.* | sayı | SUM (toplama) | Ücretsiz taban limit.maxBranches: 1 + 2× extra_branch (+1 her biri) = 3 |
feature.* | boolean | OR | Herhangi bir kaynak feature.advancedReports: true veriyorsa → açık |
integration.* | string[] | UNION (birleşim) | fiscal_efatura + fiscal_hugin → integration.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
| Metot | Yol | Kimlik | Roller | Notlar |
|---|---|---|---|---|
GET | /v1/marketplace/addons | Public | — | Katalog; yalnızca published satırlar |
GET | /v1/marketplace/addons/available | Personel JWT | ADMIN, MANAGER | Katalog + includedInPlan işareti (tenant’ın hak setine göre) |
GET | /v1/marketplace/addons/mine | Personel JWT | ADMIN, MANAGER | Tenant’ın elindeki ürünler |
POST | /v1/checkout/intent | Personel JWT | ADMIN, MANAGER | Karma-sepet checkout intent (tek satın alma yolu) |
DELETE | /v1/marketplace/addons/:id | Personel JWT | ADMIN | TenantAddOn 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.
| Kod | Tür | Fatura | Fiyat | Açtığı (grant) | Lisans | Bağımlılık |
|---|---|---|---|---|---|---|
license_annual | license | yıllık | ₺4.900 (490000) | feature.license, feature.prioritySupport, integration.fiscal += efatura | — | — |
advanced_reports | module | yıllık | ₺1.290 (129000) | feature.advancedReports | ✅ | — |
module_inventory | module | yıllık | ₺3.900 (390000) | feature.inventoryTracking | ✅ | — |
module_reservations | module | yıllık | ₺990 (99000) | feature.reservationSystem | ✅ | — |
module_personnel | module | yıllık | ₺990 (99000) | feature.personnelManagement | ✅ | — |
module_personnel_card_shift | module | tek seferlik | ₺4.000 (400000) | feature.cardShift | ✅ | module_personnel |
module_ai_studio | module | yıllık | ₺1.990 (199000) | feature.aiContentGeneration | ✅ | — |
api_access | module | yıllık | ₺2.490 (249000) | feature.apiAccess | ✅ | — |
module_external_display | module | yıllık | ₺1.990 (199000) | feature.externalDisplay | ✅ | — |
priority_support | module | yıllık | — | feature.prioritySupport | v3.6.7 arşivlendi — license_annual içine alındı | — |
delivery_platforms | integration | yıllık | ₺2.499 (249900) | integration.delivery += yemeksepeti, getir, trendyol_yemek, migros; feature.deliveryIntegration | ✅ | — |
delivery_yemeksepeti | — | — | v3.6.8 arşivlendi — delivery_platforms içine alındı | — | — | — |
delivery_getir | — | — | v3.6.8 arşivlendi — delivery_platforms içine alındı | — | — | — |
delivery_trendyol_yemek | — | — | v3.6.8 arşivlendi — delivery_platforms içine alındı | — | — | — |
fiscal_efatura | integration | yıllık | — | integration.fiscal += efatura | v3.6.7 arşivlendi — license_annual içine alındı | — |
fiscal_hugin | integration | yıllık | ₺2.990 (299000) | integration.fiscal += hugin | ✅ | — |
caller_id_integration | integration | yıllık | ₺1.490 (149000) | integration.caller += generic | ✅ | — |
sms_integration | integration | yıllık | ₺990 (99000) | integration.sms += * | ✅ | — |
extra_branch | capacity | yıllık | ₺3.990 (399000) | limit.maxBranches +1, feature.multiLocation | ✅ | — |
credit_ai_photo_100 | credit | tek seferlik | ₺690 (69000) | — (100 PHOTO kontörü) | — | module_ai_studio |
credit_ai_video_20 | credit | tek seferlik | ₺890 (89000) | — (20 VIDEO kontörü) | — | module_ai_studio |
credit_ai_3d_10 | credit | tek seferlik | ₺790 (79000) | — (10 MODEL3D kontörü) | — | module_ai_studio |
credit_sms_500 | credit | tek seferlik | ₺490 (49000) | — (500 SMS kontörü) | — | sms_integration |
onsite_install_full | service | tek 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:
code | Anlamı |
|---|---|
LICENSE_REQUIRED | Ürün lisans ister; sepette de lisans yok |
ADDON_ALREADY_OWNED | Bu ürün (ya da lisans) zaten aktif |
ADDON_ALREADY_GRANTED | Ürünün verdiği her şey zaten yetki setinde var |
ADDON_REQUIRES_DEPENDENCY | Bağımlı olduğu ürün ne sahiplikte ne sepette |
ADDON_LIMIT_REDUNDANT | Eklediği kapasite zaten sınırsız |
ADDON_MAX_QUANTITY | Katalog 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_500 → sms_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", "...": "..." }
}
]| Alan | Anlamı |
|---|---|
status | active · 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) |
chargedCents | Bu dönem için gerçekten tahsil edilen (orantılı) tutar; comp’larda 0 |
origin | purchase · 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_REQUIREDzarfı. - 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_accessmodülüfeature.apiAccess’i açar). - Operatör deneyimi (panelde ürünlere göz atma, satın alma ve takip): Marketplace yardımı.