Finansfatura 0.4.0
dotnet add package Finansfatura --version 0.4.0
NuGet\Install-Package Finansfatura -Version 0.4.0
<PackageReference Include="Finansfatura" Version="0.4.0" />
<PackageVersion Include="Finansfatura" Version="0.4.0" />
<PackageReference Include="Finansfatura" />
paket add Finansfatura --version 0.4.0
#r "nuget: Finansfatura, 0.4.0"
#:package Finansfatura@0.4.0
#addin nuget:?package=Finansfatura&version=0.4.0
#tool nuget:?package=Finansfatura&version=0.4.0
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
- API referansı: https://docs.finansfatura.com
- Uçtan uca entegrasyon kılavuzu:
integration_guide.md - Destek: partner@finansfatura.com —
invoice_id/transaction_id/external_idve isteğin UTC zaman damgasıyla yazın.
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 | Versions 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. |
-
.NETStandard 2.0
- System.Text.Json (>= 8.0.5)
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.