# Fatura Uygulamaları

> Tecof’un fatura uygulamaları, kurumsal/bireysel alıcı, ortak invoice metafield’ı, para kuralları ve @tecof/invoice-kit.
>
> Kaynak: https://tecof-app-dev.vercel.app/app-invoicing · Bölüm: Uygulama geliştirme · Son güncelleme: 2018-10-20

Tecof'ta fatura kesen her uygulama aynı sipariş verisini okur, aynı para kurallarını uygular ve faturanın durumunu siparişe aynı biçimde yazar. Böylece mağaza hangi entegratörü seçerse seçsin fatura tutarı aynı çıkar; kargo, muhasebe ya da raporlama uygulamaları da faturanın kesilip kesilmediğini sağlayıcıyı bilmeden öğrenir.

Bu sayfa üç şeyi anlatır: Tecof'un kendi fatura uygulamaları, fatura yazan bir uygulamanın uyması gereken ortak kurallar ve bu kuralları hazır veren `@tecof/invoice-kit` paketi.

---

## Tecof'un Fatura Uygulamaları

Hepsi platform uygulamasıdır (`integration: "platform"`): kimlik bilgileri [Vault](/app-data)'ta şifreli durur, siparişler [webhook](/app-webhooks) ile gelir, sipariş ekranlarında [aksiyon](/app-actions) sunar. Her birinin deposunda kurulum, ayarlar ve sınırları anlatan kendi README'si bulunur.

| Uygulama | Slug | Belge | Modlar | Fatura numarası | Sipariş iptal/iade edilince |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Logo İşbaşı | `isbasi` | İşbaşı satış faturası | kapalı · taslak · kes | İşbaşı | İşaretle ya da **yalnız taslağı** sil |
| GİB e-Arşiv Portalı | `earsiv` | e-Arşiv (portal) | kapalı · taslak (imza portalda) | GİB | İşaretle ya da **imzasız** taslağı sil |
| Paraşüt | `parasut` | Satış faturası, isteğe bağlı e-Arşiv / e-Fatura | kapalı · fatura · e-belge | Paraşüt | İşaretle ya da e-belgesi **olmayan** faturayı iptal et |
| EDM Bilişim | `edm` | e-Fatura / e-Arşiv (UBL-TR) | kapalı · taslak · gönder | EDM | İşaretle ya da e-Arşiv'i koşullu iptal et |
| QNB eSolutions | `qnb` | e-Fatura / e-Arşiv (UBL-TR) | kapalı · gönder | QNB | İşaretle (iptal QNB portalında) |
| KolayBi | `kolaybi` | Satış faturası, isteğe bağlı e-Arşiv / e-Fatura | kapalı · fatura · e-belge | KolayBi | İşaretle ya da resmileşmemiş ve tahsilatı olmayan faturayı sil |

Ortak davranışlar:

- **Tetikleyici**: `order.paid`, `order.fulfilled` ya da yalnız elle (`manual`).
- **Aksiyonlar**: sipariş detayında önizleme, oluşturma, yeniden deneme ve PDF çekmecesi (`order.detail`, iframe); sipariş listesinde "Seçilenleri faturala" (`order.list`, api, en fazla 50 sipariş).
- **e-Fatura mı e-Arşiv mi**: e-belge kesen uygulamalar alıcının e-Fatura mükellefiyetini sağlayıcıya sorar; mükellefe e-Fatura, diğerlerine e-Arşiv keser. Anonim ve yurt dışı alıcı sorgulanmadan e-Arşiv alır. Hangi alıcıların sorgulandığı sağlayıcıya göre değişir: İşbaşı ve Paraşüt yalnız kurumsal alıcıyı, EDM ve QNB kimlik numarası olan her yurt içi alıcıyı sorgular. KolayBi API'si mükellef sorgusu sunmadığı için belge türünü KolayBi belirler. Sorgu hata verirse belge **kesilmez**; mükellefe yanlışlıkla e-Arşiv gitmesin diye e-Arşiv'e düşülmez.
- **Çift fatura koruması**: uygulama, sağlayıcıyı çağırmadan önce siparişi `creating` olarak işaretler. Zaman aşımı ya da yarım kalan denemeden sonra önce sağlayıcıda sipariş numarası veya belge UUID'siyle arar; belge varsa onu benimser, yoksa yeniden dener.

> e-Fatura bu uygulamaların hiçbirinde otomatik iptal edilmez; düzeltme iade faturasıyla yapılır. e-Arşiv'i otomatik iptal eden tek uygulama EDM'dir: tam iptal ya da iade olması, fatura tarihinden itibaren 7 gün içinde kalınması ve EDM'deki belge durumunun iptale izin vermesi gerekir.

---

## Kurumsal ve Bireysel Alıcı

Checkout'ta müşteri **Bireysel** ya da **Kurumsal** fatura seçer. Seçim sipariş nesnesinin `billingAddress` alanına yazılır ([Siparişler](/app-api)):

| Alan | Bireysel | Kurumsal (Türkiye) | Kurumsal (yurt dışı) |
| :--- | :--- | :--- | :--- |
| `invoiceType` | `"individual"` | `"corporate"` | `"corporate"` |
| `company` | boş olabilir | Unvan, zorunlu | Unvan, zorunlu |
| `taxNumber` | boş | 10 haneli VKN ya da şahıs şirketi için 11 haneli TCKN; sağlama hanesi doğrulanır | Serbest biçim (harf, rakam, `-`; en fazla 30) |
| `taxOffice` | boş | Zorunlu | Zorunlu değil |
| `identityNumber` | TCKN; yoksa anonim `11111111111` | — | — |

Checkout bu kurallara uymayan siparişi `400 invalid-invoice-identity` ile reddeder. Yine de eski siparişlerde ya da elle oluşturulan kayıtlarda tutarsız veri bulunabileceği için fatura uygulamaları alıcıyı kendileri sınıflandırmalıdır. `classifyBuyer` bunu tek adımda yapar (aşağıda).

---

## Ortak Fatura Durumu

Her fatura uygulaması sipariş başına tek bir **paylaşılan** metafield yazar. Panel bu alanı sipariş detayındaki "Uygulamalar" kartında gösterir. Aynı mağazadaki diğer uygulamalar `orders:read` kapsamıyla `GET /api/v1/metafields?ownerType=order&ownerId=…` üzerinden okuyabilir; yanıtta kendi alanlarının yanında öteki uygulamaların paylaşılan alanları da gelir.

| Özellik | Değer |
| :--- | :--- |
| `ownerType` | `"order"` |
| `namespace` | Uygulamanın slug'ı (`isbasi`, `parasut`, …) |
| `key` | `"invoice"` |
| `type` | `"json"` |
| `visibility` | `"shared"` |
| `panel` | `{ label: { tr, en }, show: true }` |

```json
{
  "status": "issued",
  "documentType": "e-arsiv",
  "invoiceNumber": "ABC2026000000123",
  "uuid": "0b6c6f0e-3a7d-4a35-9a4e-5f2f6b1c9d10",
  "updatedAt": "2026-10-10T09:17:02.000Z"
}
```

`status` sözlüğü bütün fatura uygulamalarında aynıdır:

| Durum | Anlamı |
| :--- | :--- |
| `creating` | Talep alındı, sağlayıcıya gönderiliyor |
| `draft` | Sağlayıcıda taslak olarak duruyor |
| `issued` | Kesildi; e-belgeyse GİB'e gönderildi |
| `signed` | İmzalandı (portal akışlarında) |
| `failed` | Başarısız; `error` alanında temizlenmiş neden |
| `skipped` | Bilerek atlandı; neden `reason` alanında (örn. `total-mismatch`, `non-try-currency`, ayar kapalı) |
| `needs-cancellation` | Sipariş iptal ya da iade edildi; belgenin iptali elle yapılmalı |
| `deleted` | Taslak silindi |
| `cancelled` | Sağlayıcıda iptal edildi |

Değerde kişisel veri tutulmaz: alıcı adı, adresi ya da tam vergi numarası metafield'a ve olay yüküne yazılmaz.

Uygulamalar ayrıca şu [olayları](/app-interop) yayınlar; dinleyen uygulama `app.<slug>.` önekiyle abone olur:

| Olay | Ne zaman |
| :--- | :--- |
| `app.<slug>.invoice.created` | Fatura ya da taslak oluştu |
| `app.<slug>.invoice.failed` | Deneme başarısız oldu |
| `app.<slug>.invoice.cancelled` | Belge silindi ya da sağlayıcıda iptal edildi |

```ts
// Kargo uygulaması: faturası kesilmemiş siparişi kargoya vermeden önce uyarır
const fields = await api.metafields.list({ ownerType: "order", ownerId: orderId });
const issued = fields.some((m) => m.key === "invoice" && ["issued", "signed"].includes(m.value?.status));
```

---

## Ortak Para Kuralları

Fatura yazan bir uygulama, sipariş toplamıyla birebir aynı faturayı üretmek zorundadır. Kurallar `@tecof/invoice-kit` içinde uygulanmıştır; kendi hesaplamanızı yazıyorsanız aynılarını izleyin.

- **Satır KDV modu** önce satırdan (`taxIncluded`), sonra siparişten (`totals.pricesIncludeTax`) okunur. Oranı `null` olan eski satırlarda mağaza varsayılanı kullanılır.
- **KDV matrahı indirim sonrası tutardır.** Sipariş, her satıra düşen indirimi `lineItems[].discountAmount` alanında taşır; faturayı bu paylarla kurun. Kapsamlı kupon yalnız kendi satırlarına düşer. Eski siparişlerde pay yoksa indirimi ürün satırlarına tutarla orantılı dağıtın. İndirim kargo ve ödeme ücretine düşmez.
- **Kargo** ve **ödeme ek ücreti** ayrı hizmet satırıdır. Oranı bilinmeyen kargo KDV dahil sayılır.
- **Hediye çeki** ödeme aracıdır, indirim değildir: fatura tam tutardan kesilir.
- **Mutabakat**: küçük kuruş farkları (varsayılan `max(0,05; 0,01 × satır sayısı)`) en büyük satırda düzeltilir. Fark bu toleransı aşarsa `total-mismatch` uyarısı oluşur.
- **`total-mismatch`** olan sipariş otomatik ve toplu faturalamada **atlanır** (`status: "skipped"`, `reason: "total-mismatch"`). Mağaza sipariş çekmecesinde farkı görüp onaylayarak "Farka rağmen oluştur" diyebilir.
- **TRY dışı** sipariş: Paraşüt ve KolayBi uygulamaları ayar açıksa döviz faturasını TCMB kuruyla keser; diğer uygulamalar bu sürümde yalnız TRY faturalar ve döviz siparişini `skipped` olarak işaretler. `toInvoiceDraft` döviz siparişinde kur ister (`exchange-rate-missing`).

---

## @tecof/invoice-kit

Siparişi ([`GET /api/v1/orders/:id`](/app-api)) doğru bir Türk faturasına çeviren, bağımlılıksız TypeScript paketidir. Tecof'un bütün fatura uygulamaları bunu kullanır.

```bash
npm install @tecof/invoice-kit
```

```ts
import { toInvoiceDraft, buildUblInvoice } from "@tecof/invoice-kit";

const order = await api.orders.get(orderId);
const draft = toInvoiceDraft(order, { defaultVatRate: 20 });

if (draft.warnings.some((w) => w.code === "total-mismatch")) {
  // Otomatik akışta atla; kararı mağazaya bırak
}

// e-Fatura / e-Arşiv entegratörü UBL bekliyorsa
const xml = buildUblInvoice(draft, {
  profile: "EARSIVFATURA",
  typeCode: "SATIS",
  uuid: crypto.randomUUID(),
  supplier,
});
```

| Dışa aktarım | Ne yapar |
| :--- | :--- |
| `toInvoiceDraft(order, options?)` | Alıcı, satırlar, KDV dökümü, toplamlar, düzenleme tarihi (Europe/Istanbul) ve uyarılar. Hata fırlatmaz; faturayı kesip kesmemek çağıranın kararıdır |
| `classifyBuyer(order)` | `corporate`, `individual`, `anonymous` ya da `foreign`; kimlik numarası, unvan, vergi dairesi ve uyarılar |
| `buildUblInvoice(draft, options)` | İmzasız UBL-TR 1.2 fatura XML'i (TEMELFATURA, TICARIFATURA, EARSIVFATURA) |
| `isValidVkn`, `isValidTckn` | Sağlama hanesi doğrulaması; checkout ile aynı algoritma |
| `amountInWords(tutar)` | "Yalnız Bin İki Yüz Otuz Dört Türk Lirası Elli Kuruş" |
| `suggestEArchivePaymentMethod(provider)` | Tecof ödeme sağlayıcısını GİB ödeme şekline çevirir |

Alıcı sınıflandırması:

1. Fatura adresi yurt dışıysa `foreign` olur. Kurumsalda kimlik `2222222222` kullanılır, alıcının kendi vergi numarası nota ve UBL'e ayrıca yazılır.
2. `invoiceType: "corporate"` ve geçerli VKN ya da TCKN varsa `corporate` olur. Numara geçersizse bireysel kurala düşer ve `tax-number-invalid` uyarısı oluşur.
3. Geçerli TCKN varsa `individual`, yoksa `anonymous` (`11111111111`) olur.

Bilinen sınırlar: ihracat, tevkifat, ÖTV ve özel matrah faturaları desteklenmez. Paket belgeyi imzalamaz ve zarf üretmez; bunlar entegratörün işidir. Alıcının e-Fatura mükellefi olup olmadığını da sorgulamaz; bunu entegratörden öğrenmeniz gerekir.

---

## Kendi Fatura Uygulamanızı Yazarken

- Kimlik bilgilerini yalnız [Vault](/app-data)'ta tutun; arayüze ve günlüklere maskeli yazın.
- Sağlayıcı adresi ayarlanabiliyorsa yalnız `https` ve sağlayıcının kendi alan adlarına izin verin. Aksi hâlde saklanan kimlik bilgileri başka bir sunucuya gönderilebilir.
- Siparişi her zaman kurulum token'ıyla okuyun. Başka mağazanın siparişi `404` döner; panelden gelen sipariş kimliğine bu kontrol olmadan güvenmeyin.
- Webhook'u 8 saniye içinde yanıtlayın, sağlayıcı çağrısını arka plana alın. Aynı olay birden fazla gelebilir; `creating` işaretiyle çift faturayı önleyin.
- Durumu yukarıdaki sözleşmeyle yazın, olayları yayınlayın. Sağlayıcı hata mesajlarını göstermeden önce oturum kimliği, parola ve tam vergi numarasından arındırın.
- GİB'e gitmiş bir belgeyi kural dışında iptal etmeyin; emin değilseniz `needs-cancellation` işaretleyip kararı mağazaya bırakın.

Uçtan uca örnek için [Örnek Uygulama: Logo İşbaşı](/app-example-isbasi) sayfasına bakın.
