Finansfatura 0.4.0

dotnet add package Finansfatura --version 0.4.0
                    
NuGet\Install-Package Finansfatura -Version 0.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Finansfatura" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Finansfatura" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="Finansfatura" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Finansfatura --version 0.4.0
                    
#r "nuget: Finansfatura, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Finansfatura@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Finansfatura&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=Finansfatura&version=0.4.0
                    
Install as a Cake Tool

Finansfatura .NET istemcisi

Finansfatura e-belge API'si için resmî .NET istemcisi: siparişi satışa çevirir, belgeyi keser, durumunu izler.

dotnet add package Finansfatura

netstandard2.0 ve net8.0 hedefler — .NET Framework 4.6.1+ ve modern .NET'te çalışır. Tek bağımlılık System.Text.Json (yalnız netstandard2.0 hedefinde).

Hızlı başlangıç

using Finansfatura;

using var ff = new FinansfaturaClient(new FinansfaturaClientOptions
{
    ApiKey  = Environment.GetEnvironmentVariable("FF_KEY"),
    BaseUrl = FinansfaturaClient.SandboxBaseUrl,
});

// 1) Satış — fiyatlar KDV DAHİL, oran YÜZDE
var sale = await ff.CreateOrderAsync(new Order
{
    Provider      = "OTELPLATFORM",
    ExternalId    = "otel-1042",
    OrderNumber   = "1042",
    PaymentStatus = "PAID",
    Currency      = "TRY",
    TotalPrice    = 4321.00m,
    Buyer = new OrderBuyer
    {
        Title     = "Ahmet Yılmaz",
        Tckn      = "11111111111",
        TaxOffice = "Beşiktaş",                       // adres varsa bunu da gönderin
        Address   = "Barbaros Mah. No:1 Beşiktaş / İstanbul",
        Email     = "ahmet@example.com",
        Phone     = "5551112233",
    },
    Lines = new[]
    {
        new OrderLine { Title = "Konaklama", Sku = "ODA-STD", Quantity = 1,
                        UnitPrice = 4321.00m, TotalPrice = 4321.00m, VatRate = 20m },
    },
});

// 2) Fatura — tutarlar KDV HARİÇ, oran ONDALIK
var (net, _) = Money.SplitGross(4321.00m, 0.20m);      // 3600.83

var payload = Payload.BuildEarsiv(
    new CanonicalParty
    {
        VKNorTCKN = "11111111111", Title = "Ahmet Yılmaz",
        TaxOffice = "Beşiktaş",   Address = "Barbaros Mah. No:1 Beşiktaş / İstanbul",
        Email = "ahmet@example.com", Phone = "5551112233",
    },
    new[] { new LineInput { Title = "Konaklama", ProductCode = "ODA-STD",
                            Quantity = 1, UnitPrice = net, VatRate = 0.20m } },
    transactionHeaderId: sale.TransactionId);

var invoice = await ff.IssueInvoiceAsync(payload, idempotencyKey: "otel-1042");
Console.WriteLine($"{invoice.Status} ETTN={invoice.Ettn}");

// 3) Durum — fatura numarası kesim yanıtında değil, burada dolar
var st = await ff.OrderStatusAsync("OTELPLATFORM", new[] { "otel-1042" });

Bilinmesi gereken dört şey

Sıra sabittir. Önce satış, sonra fatura. Fatura ucu satışa bağlanmamış belge kabul etmez — tek istisna iadedir (invoiceTypeCode: "IADE"), o satışa bağlanmaz. Payload.Build* bunu istek gitmeden doğrular.

İki uç birbirinin tersi konuşur.

Satış ucu Fatura ucu
Fiyatlar KDV dahil (4321.00) KDV hariç (3600.83)
KDV oranı yüzde (20) ondalık (0.20)

Money.SplitGross dönüşümü yapar; KDV'yi çıkarmayla bulur, böylece net + kdv == brüt her zaman sağlanır. Ayrı yuvarlarsanız 1 kuruş kayar ve GİB şematronu belgeyi reddeder.

Idempotency-Key zorunlu. Sipariş kimliğinizi verin: aynı anahtarla tekrar denemek ikinci belge kesmez, kontörü iki kez düşmez. external_id ile birlikte tüm retry'larınız güvenli olur.

Alan yazımı iki katmanlı. Dış zarf snake_case, canonical içi PascalCase. canonical içine snake_case bir anahtar konursa sunucu onu sessizce yok sayar — bu yüzden gövdeyi elle kurmayın, Payload.Build* kullanın.

KDV istisnası, senaryo, iade, tevkifat

%0 KDV'li satır istisna sebebi olmadan faturalanamaz — GİB reddediyor. Sebep belge düzeyindedir ve yalnız %0 satırlara iner; KDV'li satıra iliştirilmez, yoksa karışık belgede istisna beyanı vergili satırı da kapsamış görünürdü.

var payload = Payload.BuildEarsiv(recipient, new[]
{
    new LineInput { Title = "Mal ihracatı", Quantity = 1m, UnitPrice = 1000m, VatRate = 0m },
    new LineInput { Title = "Kargo", Quantity = 1m, UnitPrice = 100m, VatRate = 0.20m },
}, sale.TransactionId,
   exemptionCode: "301",                      // GİB 2xx (kısmi) / 3xx (tam)
   exemptionReason: "11/1-a Mal ihracatı");

İkisi birlikte zorunlu: GİB boş cbc:TaxExemptionReason kabul etmiyor, metinsiz kod da bir şey beyan etmiyor. Satışta InvoiceExemptionCode + InvoiceExemptionReason gönderin — sebep satışta saklanır, fatura hangi yoldan kesilirse kesilsin oradan okunur.

Fatura tipini göndermeyin: sunucu satırlardan türetir (%0 KDV + istisna → ISTISNA, tevkifat → TEVKIFAT, özel matrah → OZELMATRAH).

Senaryo (yalnız e-Fatura)

TEMELFATURA ya da TICARIFATURA. Fark hukukidir, görünüm değil: TEMEL'de alıcı yanıt veremez, belge kesindir; TİCARİ'de 8 gün içinde KABUL/RED gönderebilir. Varsayılan TICARIFATURA; e-Arşiv'de seçim yoktur.

Payload.BuildEfatura(recipient, lines, sale.TransactionId, scenario: "TEMELFATURA");

İade

İade kendi belgesidir ve satışa bağlanmaz — bağlanırsa aynı satış iki kez sayılır.

await ff.RefundAsync(new Refund {
    ExternalId = "REF-2026-0007",            // iadenin kendi kimliği
    OrderExternalId = "ORD-2026-00184",      // iade edilen satış
    TotalPrice = 120m,
    Lines = new[] { /* satışla aynı şekil, POZİTİF tutarlar */ },
    Buyer = buyer,
});

Dövizli satışın iadesi asıl satışın kurunu kullanır, bugünün kurunu değil: ExchangeRate göndermezseniz satıştan okuruz. Aylar önceki bir satışı bugünün kuruyla iade etmek yanlış beyandır.

İade belgesini kendiniz kuruyorsanız, iade edilen asıl faturanın atfı gerekir — GİB atıfsız iadeyi reddediyor:

Payload.BuildEarsiv(recipient, lines,
    invoiceTypeCode: "IADE",                 // ya da TEVKIFATIADE / YTBIADE
    returnInfo: ("FF32026000000123", "2026-09-27"));

Tarih sizin için RFC 3339'a çevrilir; çıplak 2026-09-27 sunucuda ayrıştırılamaz. Currency / ExchangeRate de yalnız burada anlamlıdır — satışta sunucu ikisini de satıştan alır.

Tevkifat ve özel matrah

İkisi de satır bazında ve zıt durumlar: tevkifat KDV'yi kimin ödediğini böler, özel matrah KDV'nin neyin üzerinden hesaplandığını değiştirir.

Payload.BuildEfatura(recipient, new[]
{
    new LineInput { Title = "Temizlik", Quantity = 1m, UnitPrice = 1000m, VatRate = 0.20m,
                    WithholdingCode = "612", WithholdingName = "Temizlik hizmeti" },
    new LineInput { Title = "İkinci el araç", Quantity = 1m, UnitPrice = 550000m, VatRate = 0.20m,
                    TaxBaseAmount = 50000m, TaxBaseCode = "812", TaxBaseReason = "Kâr marjı" },
}, sale.TransactionId);

Tevkifat oranı gönderilmez — modelde alan bile yok. Her GİB kodunun yasal oranı sabittir ve sunucu oranı koddan türetir: 612 (temizlik) 2023'te 7/10'dan 9/10'a çıktı. Oran sizden gelseydi, GİB bir güncelleme yaptığında entegrasyonunuz yıllarca yanlış beyan üretebilirdi.

8xx kodları iki listede de var (tevkifat 801-825, özel matrah 801-812) ama farklı UBL elemanları ve farklı alanlar.

Döviz kuru

Kuru göndermek zorunda değilsiniz: ExchangeRate boş (ya da 0) bırakılan dövizli satışta sunucu TCMB kurunu doldurur ve ne kullandığını yanıtta söyler (exchange_rate, exchange_rate_source: "TCMB", exchange_rate_date). Kendi kurunuzu gönderirseniz aynen kullanılır, TCMB ile karşılaştırılmaz.

Kur uydurulmaz: bülten o para birimini taşımıyorsa istek ERROR_EXCHANGE_RATE_REQUIRED döner. Bülten hafta içi ~15:30'da yayımlanır, hafta sonu yayımlanmaz — pazartesi sabahı kesilen belge cumanın kurunu taşır ve dönen tarih bunu açık eder.

Hata yönetimi

Her başarısız çağrı tipli bir hata fırlatır:

try
{
    await ff.IssueInvoiceAsync(payload, "otel-1042");
}
catch (ProviderRejectedException ex)          // 422 — sağlayıcı belgeyi reddetti
{
    // Sebep burada. Belge düzeltilmeden tekrar denemek boşa gider.
    log.Error("ret {Code}: {Message}", ex.ProviderCode, ex.ProviderMessage);
}
catch (ValidationException ex)                // 400 — alan bazlı detay
{
    foreach (var f in ex.ValidationErrors) log.Error("{Field}: {Message}", f.Field, f.Message);
}
catch (FinansfaturaException ex) when (ex.Retryable)   // 429 / 5xx
{
    // Artan aralıklarla, AYNI Idempotency-Key ile tekrar deneyin.
}
Tip HTTP Tekrar denenir mi
ValidationException 400 ❌
AuthException 401 ❌ önce kimliği yenileyin
InsufficientCreditsException 402 ❌
ScopeException 403 ❌
OnboardingRequiredException 412 ❌ mükellef kurulumu bitirmeli
ProviderRejectedException 422 ❌ belgeyi düzeltin
RateLimitException 429 ✅
ProviderException 5xx ✅

ProviderRejectedException.ProviderMessage ret sebebini sağlayıcının kendi metniyle taşır. Destek kaydınıza bunu geçirin: HTTP kodu tek başına hiçbir şey söylemez.

OAuth

Çok mükellefli ürünlerde API anahtarı yerine:

using var oauth = new OAuth(new OAuthOptions
{
    ClientId = cid, ClientSecret = secret,
    RedirectUri = "https://app.example.com/ff/callback",
});

var pkce = OAuth.GeneratePkce();                     // verifier'ı oturumda tutun
return Redirect(oauth.BuildAuthorizeUrl(pkce.Challenge, state: csrfToken));

// ... geri dönüşte ?code=... ...
var token = await oauth.ExchangeCodeAsync(code, pkce.Verifier);
using var ff = new FinansfaturaClient(new FinansfaturaClientOptions { AccessToken = token.AccessToken });

Desteklenen akışlar authorization_code ve refresh_token'dır; client_credentials yoktur. Her yenileme bir öncekini geçersizler — elinize geçen en yeni RefreshToken'ı saklayın.

DI ile kullanım

HttpClient'ı dışarıdan verin; istemci ona dokunmaz, Dispose etmez:

services.AddHttpClient("finansfatura");
services.AddScoped(sp => new FinansfaturaClient(new FinansfaturaClientOptions
{
    ApiKey     = cfg["Finansfatura:ApiKey"],
    HttpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("finansfatura"),
}));

Dokümantasyon

Geliştirme

dotnet build src/Finansfatura/Finansfatura.csproj
dotnet test  tests/Finansfatura.Tests/Finansfatura.Tests.csproj
dotnet pack  src/Finansfatura/Finansfatura.csproj -c Release

MIT.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 78 10/4/2026
0.3.1 100 9/12/2026