In-App Purchase (IAP) ✓ Canlı Tenant'tan tek-seferlik ödeme alın (premium kilit, kredi paketi vb.). Akış: purchases/create ile checkout başlatın → tenant Stripe Checkout'ta öder → purchase.granted webhook'u düşer (kilidi açın) → purchases/get ile kesin doğrulayın.
← Para Kazanma (Komisyon) · ilgili: Payout . Ortak kurallar (base, auth, hata zarfı): API Uçları .
Akış & durum makinesiMutlu yol: pending → paid → granted. Para iadesi (refund) hem paid hem granted durumundan mümkündür → refunded. Ödeme alınmadan checkout düşerse pending → failed | canceled.
durum makinesi
Kopyalapending ──öde──► paid ──grant──► granted
│ │ │
│ failed/canceled│ refund │ refund
▼ ▼ ▼
failed/canceled refunded refunded status Anlam Terminal mi? pending Checkout başlatıldı, ödeme henüz tamamlanmadı. hayır paid Tenant ödedi; grant işleniyor. hayır (refund olabilir) granted Hak teslim edildi (kilidi açabilirsiniz). hayır (refund olabilir) refunded İade edildi (paid veya granted'den). evet failed Ödeme başarısız. evet canceled Tenant checkout'u iptal etti. evet
Grant durumunu
webhook'a bağlamayın : kesin gerçek için her zaman
purchases/get ile doğrulayın (aşağıda).
Scope'larIAP iki scope kullanır. PII değildir — finansal alanlar (net / developerShare / Stripe ID) hiçbir uçtan sızmaz.
Checkout başlat — POST /plugin-api/purchases/createMethod / yol POST /plugin-api/purchases/create Auth install API key — Authorization: Bearer serverId.pluginId.secret (token ) Scope purchases:writeRate limit write kovası — Limitler
İstek
KopyalaPOST {RESTOMENUM_BASE}/plugin-api/purchases/create
Authorization: Bearer <apiKey> // install API key: serverId.pluginId.secret
Content-Type: application/json
{
"amount": 10000, // zorunlu — GROSS kuruş (100 = 1,00₺)
"productKey": "premium_unlock", // ops — opaque SKU (sizin tarafınızda anlamlı)
"description": "Premium kilit", // ops
"idempotencyKey": "buy-9af2", // ops AMA ÖNERİLİR (retry'da çift checkout engeller)
"successUrl": "https://app.restomenum.com/...", // ops (https + restomenum.com/.app domaini)
"cancelUrl": "https://app.restomenum.com/..." // ops (aynı kural)
} İstek alanları Alan Tip Zorunlu Açıklama amount int ✓ GROSS tutar kuruş cinsinden. min 100 (1,00₺), max 5.000.000 (50.000,00₺). Aralık dışı → plugin.iap.badAmount. productKey string – Opaque SKU — sizin tarafınızda anlamlı (Restomenum yorumlamaz). description string – Tenant'a gösterilecek kısa açıklama. idempotencyKey string – Kendi benzersiz anahtarınız. Retry güvenli (aşağıdaki davranışa bkz). successUrl string (https) – Ödeme sonrası dönüş. https + hostname restomenum.com/.app veya alt alan adı olmalı; PATH doğrulanmaz . Geçersizse default kullanılır. cancelUrl string (https) – İptal dönüşü — aynı kural (geçersiz → default).
Yanıt
Kopyala// başarı (200)
{
"success": true,
"data": {
"url": "https://checkout.stripe.com/c/pay/...", // tenant'ı buraya yönlendir
"sessionId": "cs_test_...", // yalnız referans
"purchaseId": "9b1f2c3d" // KANONİK — sakla, get/list ile bununla sorgula
}
}
// hata
{ "success": false, "message": "<kod>" } data.purchaseId kanonik kimliktir — saklayın; get/list ile bununla sorgulayın. sessionId yalnız referanstır.
Hatalar HTTP message Anlam 403 plugin.iap.disabled IAP bu eklenti için kapalı. 403 plugin.billing.disabled Faturalama kapalı. 403 plugin.scope.denied purchases:write onaylı değil.403 plugin.billing.connectFirst Önce OAuth Connect gerekli. 409 plugin.iap.alreadyPurchased Tamamlanmış satın alma var (idempotencyKey ile, aşağı bkz). 400 plugin.iap.badAmount amount aralık dışı (min 100, max 5.000.000).400 plugin.iap.notFound İlgili IAP kaydı bulunamadı. 400 plugin.billing.notInstalled Faturalama kurulmamış. 500 — Sunucu hatası.
idempotencyKey davranışı (retry güvenli): Aynı anahtarla tamamlanmış (paid/granted) satın alma → 409 plugin.iap.alreadyPurchased (url YOK ). Hâlâ pending ise → aynı purchaseId ile taze url (200). Çift Stripe session açılmaz; tekrar denemek güvenlidir. Satın alma detayı — GET /plugin-api/purchases/getMethod / yol GET /plugin-api/purchases/get?purchaseId= Scope purchases:readRate limit callback kovası — Limitler purchaseId ≤200 karakter; / içeremez.
İstek
KopyalaGET {RESTOMENUM_BASE}/plugin-api/purchases/get?purchaseId=9b1f2c3d
Authorization: Bearer <apiKey> Yanıt (granted)
Kopyala// başarı — yalnız allowlist alanları döner (toPublic)
{
"success": true,
"data": {
"purchaseId": "9b1f2c3d",
"status": "granted",
"type": "one_time",
"productKey": "premium_unlock",
"description": "Premium kilit",
"amount": 10000,
"currency": "try",
"createdAt": 1718200000000,
"paidAt": 1718200025000,
"grantedAt": 1718200030000,
"refundedAt": null
}
} Allowlist (toPublic). Yalnız şu alanlar döner: purchaseId, status, type, productKey, description, amount, currency, createdAt, paidAt, grantedAt, refundedAt. Finansal/iç alanlar (net, developerShare, platformShare, Stripe ID, developerId, testMode) ASLA sızmaz .
Hatalar HTTP message Anlam 403 plugin.scope.denied purchases:read onaylı değil.404 plugin.purchases.notFound Kayıt yok veya sahibi farklı. 400 plugin.purchases.missingParams purchaseId boş, >200 karakter veya / içeriyor.
Satın almaları listele — GET /plugin-api/purchases/listMethod / yol GET /plugin-api/purchases/list Scope purchases:readlimit ops — default 50 , max 100 . Sıralama En yeni önce (createdAt desc). Kapsam Yalnız çağıran install'ın (serverId + pluginId) kayıtları.
İstek
KopyalaGET {RESTOMENUM_BASE}/plugin-api/purchases/list?limit=50
Authorization: Bearer <apiKey> Yanıt
Kopyala// başarı — en yeni önce (createdAt desc); yalnız çağıran install'ın kayıtları
{
"success": true,
"data": [
{ "purchaseId": "9b1f2c3d", "status": "granted", "amount": 10000, "currency": "try", "createdAt": 1718200000000, ... },
{ "purchaseId": "7a2e1b0c", "status": "refunded", "amount": 20000, "currency": "try", "createdAt": 1718100000000, ... }
]
} Her kayıt aynı allowlist'le döner (get ile aynı alanlar).
Hatalar HTTP message Anlam 403 plugin.scope.denied purchases:read onaylı değil.400 — Geçersiz limit.
purchase.granted webhookSatın alma teslim edilince purchase.granted event'i webhook'unuza düşer → kilidi açın. Scope purchases:read ; PII yok . Standart imzalı pipeline (SSRF guard, HMAC, retry) ile gelir — imza doğrulama ve /webhook alıcısı .
purchase.granted (envelope)
Kopyala{
"id": "9b1f2c3d_granted",
"type": "purchase.granted",
"version": "1",
"tenantId": "<tenantId>",
"occurredAt": 1718200030000,
"data": {
"purchaseId": "9b1f2c3d",
"pluginId": "plugin_abc",
"type": "one_time",
"productKey": "premium_unlock",
"amount": 10000,
"currency": "try"
}
} data alanları: purchaseId, pluginId, type, productKey, amount (kuruş), currency. Katalog: Event Kataloğu .
Webhook = kolaylık, purchases/get = KESİN GERÇEK. Webhook teslimi garanti değildir ; grant durumunu webhook'a bağlamayın . Her zaman purchases/get ile authoritatively doğrulayın — böylece paid'de takılı kalmazsınız. Idempotency / dedup. Event id deterministiktir: <purchaseId>_granted (örn. 9b1f2c3d_granted). Teslim at-least-once 'tur → aynı id tekrar gelebilir; id ile dedup edin ve işleminizi idempotent kurun. Event satın alma başına bir kez emit edilir (durum makinesi guard'ı).