# Ücretlendirme ve Hak Ediş

> Uygulama planları, KDV, abonelik durumları, %80 geliştirici payı ve partner ödemesi.
>
> Kaynak: https://tecof-app-dev.vercel.app/app-billing · Bölüm: Uygulama geliştirme · Son güncelleme: 2018-10-20

Uygulamanız aylık ya da yıllık planlar satabilir. Tahsilatı Tecof yapar (iyzico); KDV hariç tutarın varsayılan **%80**'i iade penceresi dolduktan sonra partner defterinize yazılır ve partner programının aylık ödeme akışıyla size ödenir. Planı olmayan uygulama ücretsizdir.

---

## Planlar

Planlar `tecof.app.json` içindeki `plans` dizisinde tanımlanır (en fazla 4):

```json
{
  "plans": [
    {
      "key": "standart",
      "name": { "tr": "Standart", "en": "Standard" },
      "description": { "tr": "Küçük mağazalar için", "en": "For small stores" },
      "price": 199,
      "currency": "TRY",
      "interval": "month",
      "trialDays": 14,
      "features": [
        { "tr": "Sınırsız fatura", "en": "Unlimited invoices" },
        { "tr": "Toplu faturalama", "en": "Bulk invoicing" }
      ]
    },
    {
      "key": "yillik",
      "name": { "tr": "Yıllık", "en": "Yearly" },
      "description": { "tr": "İki ay hediye", "en": "Two months free" },
      "price": 1990,
      "currency": "TRY",
      "interval": "year",
      "trialDays": 0,
      "features": [{ "tr": "Standart planın tüm özellikleri", "en": "Everything in Standard" }]
    }
  ]
}
```

| Alan | Kural |
| :--- | :--- |
| `key` | Plan anahtarı (uygulama içinde tekil) |
| `name`, `description` | `{ tr, en }` |
| `price` | **KDV hariç**, sıfırdan büyük |
| `currency` | `TRY`, `USD`, `EUR` |
| `interval` | `month` ya da `year` |
| `trialDays` | 0–30 |
| `features` | `{ tr, en }` listesi; plan kartında gösterilir |

Onaylanmış uygulamada plan değişiklikleri canlıya hemen yansır. Ücretli plan içeren `public` bir uygulamayı incelemeye göndermek için partner doğrulamanız (KYC + IBAN) onaylı olmalıdır. `private` dağıtım incelemeye girmediği için yayında KYC istenmez; ancak hak ediş **ödemesi** her durumda onaylı KYC ve IBAN ister. Bkz. [Yayınlama ve İnceleme](/app-publishing).

### KDV

Mağazanın ödeyeceği tutar:

```text
ödenecek = price × (1 + KDV oranı)
```

| Para birimi | KDV oranı |
| :--- | :--- |
| `TRY` | Tecof fatura ayarlarındaki KDV oranı (bugün %20) |
| `USD`, `EUR` | Tecof fatura ayarlarındaki ihracat KDV oranı (varsayılan %0) |

Fiyatı her zaman KDV hariç girin; katalog ve ödeme ekranı KDV'yi ekleyerek gösterir.

---

## Mağaza Satın Alma Akışı

```mermaid
sequenceDiagram
    participant M as Mağaza (panel)
    participant T as Tecof
    participant I as iyzico
    participant A as Uygulamanız

    M->>T: Kurulum: POST /api/merchant/apps/install { appId, acceptedScopes, planKey }
    alt Ücretsiz plan ya da deneme süresi var
        T-->>M: next.type = "open"
        T->>A: app.installed (+ deneme varsa app.subscription.updated: trialing)
    else Ücretli, denemesiz plan
        T-->>M: next.type = "checkout" (billing.status = pending)
        M->>T: POST /installations/:id/billing/checkout { planKey, customer }
        T-->>M: iyzico ödeme formu
        M->>I: Kart bilgisi
        I->>T: Dönüş (callback) → iyzico'dan geri sorgu
        T->>T: Dönem açılır / uzatılır
        T->>A: app.subscription.updated (active)
        T-->>M: /app/apps/my-apps/<kurulum>?billing=success
    end
```

- Ödeme iyzico'nun tek seferlik ödeme formuyla alınır; her ödeme bir dönem (ay ya da yıl) açar ya da uzatır.
- Ödeme dönüşü (callback) kaçırılırsa bir mutabakat işi `pending` kalan ödemeleri iyzico'dan sorgulayıp tamamlar.
- Panel dönüşte `billing=success`, `billing=error` ya da `billing=processing` gösterir.
- Mağaza plan seçim ekranını panelden açabilir; uygulamanızın arayüzünden açmak için App Bridge `openBilling()` kullanın.

Mağazanın gördüğü uçlar (bilgi için): `GET /api/merchant/apps/installations/:id/billing` → `{ plans, subscription, charges }`, `POST /api/merchant/apps/installations/:id/billing/checkout`.

---

## Abonelik Durumları

| Durum | Anlamı |
| :--- | :--- |
| `trialing` | Deneme süresinde |
| `active` | Ödenmiş dönem sürüyor |
| `past_due` | Dönem bitti, yenileme ödemesi bekleniyor (3 günlük tolerans) |
| `expired` | Tolerans doldu, ödeme yapılmadı |
| `cancelled` | Uygulama kaldırıldı |

```mermaid
stateDiagram-v2
    [*] --> trialing: Kurulum (denemeli plan)
    [*] --> active: Ödeme
    trialing --> active: Ödeme
    trialing --> past_due: Deneme bitti, ödeme yok
    active --> active: Yenileme ödemesi (dönem uzar)
    active --> past_due: Dönem sonu
    past_due --> active: Ödeme
    past_due --> expired: 3 gün tolerans doldu
    expired --> active: Ödeme
    trialing --> cancelled: Kaldırma
    active --> cancelled: Kaldırma
    past_due --> cancelled: Kaldırma
    expired --> cancelled: Kaldırma
```

### v1 yaşam döngüsü kuralları

- **Otomatik yenileme yoktur (v1).** Dönem sonundan 7 gün önce (varsayılan) mağazaya hatırlatma gider; mağaza yenilemeyi elle öder.
- Dönem bitince abonelik `past_due` olur; 3 günlük (varsayılan) tolerans sonunda `expired`'a geçer.
- **Deneme süresi** ödeme yapılmadan biterse de aynı yol izlenir: `trialing` → `past_due` (3 gün) → `expired`.
- Bir mağaza aynı uygulamanın deneme süresini **yalnız bir kez** kullanabilir; kaldırıp yeniden kurmak ikinci deneme açmaz.
- Uygulama kaldırılınca abonelik `cancelled` olur; **otomatik iade yapılmaz**.
- Her durum geçişi uygulamanıza `app.subscription.updated` webhook'u gönderir:

```json
{
  "event": "app.subscription.updated",
  "data": {
    "planKey": "standart",
    "status": "active",
    "trialEndsAt": null,
    "currentPeriodEnd": "2026-11-10T09:16:10.000Z",
    "chargeId": "6720dd00ee11ff22aa33bb66"
  }
}
```

Otomatik yenilenen abonelik (iyzico abonelik API'si) **v2**'de planlanmıştır.

---

## Özellikleri Plana Göre Açmak

Erişimi kısıtlamak **uygulamanın kararıdır**; Tecof süresi dolan abonelikte panelde yalnız bir uyarı bandı gösterir. Plan durumunu şu yerlerden öğrenirsiniz:

| Yer | Kullanım |
| :--- | :--- |
| App Bridge bağlamı | `context.installation.plan` = `{ key, status }` ya da `null` — arayüzde göstermek/gizlemek için |
| `app.subscription.updated` webhook'u | Sunucu tarafında durumu saklamak için |
| `api.installation()` | Sunucu tarafında anlık durum (`subscription`) |

**Sunucu tarafı örnek** — webhook ile gelen durumu ayarlarda tutup her işte kontrol etmek:

```ts
// lib/billing.ts
import { tecof } from "@/lib/tecof";

type PlanState = { planKey: string | null; status: string; currentPeriodEnd: string | null };

const ALLOWED = new Set(["trialing", "active", "past_due"]); // past_due: 3 günlük toleransta çalışmayı sürdür

export async function savePlanState(installationId: string, data: PlanState) {
  await tecof.client(installationId).settings.set({ billing: data }, { merge: true });
}

export async function hasPaidAccess(installationId: string): Promise<boolean> {
  const { settings } = await tecof.client(installationId).settings.get();
  const billing = settings.billing as PlanState | undefined;
  return !!billing && ALLOWED.has(billing.status);
}
```

```ts
// app/api/webhooks/route.ts — createWebhookHandler({ app: tecof, handlers: { … } }) içinde
"app.subscription.updated": async (event) => {
  await savePlanState(event.installationId, event.data as PlanState);
},
```

Anlık durumu webhook'a bağlı kalmadan da okuyabilirsiniz: `api.installation()` yanıtındaki `subscription` alanı (plan yoksa `null`). Bkz. [API ile Veri Erişimi](/app-api).

**Arayüz tarafı örnek:**

```tsx
"use client";

import { useAppBridge, useAppContext } from "@tecof/app-bridge/react";

export function PaidFeature({ children }: { children: React.ReactNode }) {
  const bridge = useAppBridge();
  const { installation } = useAppContext();
  const status = installation.plan?.status;

  if (status === "trialing" || status === "active" || status === "past_due") return <>{children}</>;

  return (
    <div>
      <p>Bu özellik ücretli planda kullanılabilir.</p>
      <button onClick={() => bridge.openBilling()}>Planları gör</button>
    </div>
  );
}
```

`billing` anahtarını yerel ayar formunuzdaki (`settings` şeması) anahtarlardan farklı seçin. `past_due` durumunda çalışmayı sürdürüp sürdürmemek ve deneme süresinde hangi özelliklerin açık olacağı sizin ürün kararınızdır.

---

## Geliştirici Hak Edişi

| Kural | Değer |
| :--- | :--- |
| Platform komisyonu | Varsayılan **%20** → geliştirici payı **%80** |
| Hesap tabanı | Tahsilatın **KDV hariç** tutarı |
| İade penceresi | Varsayılan **14 gün** |
| Defter satırı | Ödemeden 14 gün sonra, iade edilmemişse partner defterine `kind: "accrual"`, `sourceType: "app-charge"` satırı yazılır |
| Dönem | Satırın dönemi, **ödenebilir tarihin** (ödeme + 14 gün) ayıdır (İstanbul saati) |
| Pencere içi kısmi iade | İade edilen tutar tahakkuktan düşülür; satır kalan tutar üzerinden yazılır |
| Pencere sonrası iade | Partner defterine geri kesinti (`kind: "clawback"`, `sourceType: "app-refund"`) yazılır |
| Test mağazası satın alımı | Geliştiricinin kendi test mağazalarından yapılan satın alma gerçek satış sayılmaz: satır `held` (`app-test-store`) yazılır; Tecof yöneticisi serbest bırakabilir |

Bundan sonrası partner programının mevcut ödeme akışıdır:

1. **Aylık kapanış.** Dönemi önceki ay ya da daha eski olan, ödemeye girmemiş satırlar partner × para birimi bazında toplanır.
2. **Ödeme açılma koşulları.** Partner aktif, partner doğrulaması (KYC) onaylı, partner sözleşmesi onaylanmış ve toplam asgari tutarın üstünde olmalı. Aksi hâlde satırlar sonraki aya devreder.

   | Para birimi | Asgari ödeme |
   | :--- | :--- |
   | TRY | 1.000 TL |
   | EUR | 50 € |
   | USD | 50 $ |

3. **Partner faturası.** Şahıs ve kurumsal partner, ödeme tutarı için KDV eklenmiş fatura yükler (Türkiye'de %20; yurt dışı partnerde 0). Bireysel partnerde fatura adımı yoktur.
4. **Onay ve ödeme.** Tecof yöneticisi faturayı onaylar (doğrulama ve IBAN şarttır) ve ödemeyi kaydeder. Fatura reddedilirse yeniden yüklenir.

Tahsilatları geliştirici API'sinden izleyebilirsiniz: `GET /api/developer/apps/:id/charges`.

### Sayısal örnek

Plan: **Standart**, 199 TL/ay (KDV hariç), Türkiye'deki kurumsal bir partner.

| Adım | Hesap | Tutar |
| :--- | :--- | :--- |
| Mağazanın ödediği | 199 × 1,20 | **238,80 TL** |
| KDV hariç taban | — | 199,00 TL |
| Platform komisyonu (%20) | 199 × 0,20 | 39,80 TL |
| Geliştirici payı (%80) | 199 × 0,80 | **159,20 TL** |

Zaman çizelgesi (ödeme 20 Ekim 2026):

| Tarih | Olay |
| :--- | :--- |
| 20.10.2026 | Mağaza öder; abonelik `active`, `app.subscription.updated` |
| 03.11.2026 | 14 günlük iade penceresi doldu; defterde 159,20 TL `accrual`, dönem **2026-11** |
| Aralık 2026 başı | Kasım dönemi kapanışa girer |

Aynı ay 7 mağazadan bu planla tahsilat yapıldığını varsayalım: 7 × 159,20 = **1.114,40 TL** ≥ 1.000 TL asgari tutar → ödeme açılır. Kurumsal partner 1.114,40 TL + %20 KDV = **1.337,28 TL** tutarında fatura yükler; yönetici onayından sonra ödeme yapılır. Toplam 1.000 TL'nin altında kalsaydı satırlar bir sonraki kapanışa devrederdi.

Mağaza 14 gün içinde tam iade alırsa defter satırı hiç yazılmaz; kısmi iadede tahakkuk iade oranında azalır: örneğin 238,80 TL'lik ödemenin 118,80 TL'si (KDV dahil) pencere içinde iade edilirse kalan KDV hariç tutar 199 × 120 / 238,80 = 100,00 TL, defter satırı 100,00 × 0,80 = **80,00 TL** olur. Pencereden sonra tam iade yapılırsa iade dönemine −159,20 TL'lik `clawback` satırı düşer. İadeleri (tam ya da kısmi) Tecof yöneticisi yapar.

---

## Açık Konular

- Uygulama tahsilatının mağazaya Tecof tarafından faturalanması (e-Arşiv) henüz netleşmedi.
- Otomatik yenileme v2'ye bırakıldı.
- Birinci parti (partnersiz) uygulamalarda hak ediş satırı yazılmaz.
