Satış olunca muhasebesi kendiliğinden işlensin.
Web siten, e-ticaret altyapın ya da kendi programın satışı tek bir istekle Uygun Muhasebe'ye gönderir: müşteri kartı açılır, satış deftere yazılır, tahsilat ve stok işlenir. İstersen fatura e-Fatura ya da e-Arşiv olarak da gider, müşterine e-postayla ulaşır. Uygun Muhasebe'nin kendi abonelik ve e-imza satışlarının faturası da bu yoldan kesilir.
Başlarken
- Uygun Muhasebe'de Ayarlar > API sayfasını aç (işletme sahibi ya da yönetici). API her pakette dahildir.
- Önce bir test anahtarı oluştur: test anahtarıyla kesilen fatura taslak kalır, numara almaz, gönderilmez; panelden silersin. Bağlantın çalışınca canlı anahtar oluştur.
- Anahtar yalnız oluşturulduğu an görünür. Sunucundaki ayar dosyasında sakla; tarayıcı koduna, uygulama paketine koyma.
Bütün istekler https://uygunmuhasebe.com/api/v1 altına gider, gövde ve yanıt UTF-8 JSON'dur. Her isteğe anahtarını başlık olarak ekle:
Authorization: Bearer tzk_canli_xxxxxxxxxxxxxxxx
Content-Type: application/json
Bağlantıyı denemek için:
curl https://uygunmuhasebe.com/api/v1/ping -H "Authorization: Bearer $UYGUN_MUHASEBE_ANAHTARI"
{"firma": "Örnek Ticaret Ltd. Şti.", "anahtar": "Web sitesi", "test": true, "e_belge": true, "kayit_turu": "e_belge"}
Anahtar onu oluşturan kişinin adına çalışır: kişinin satış faturalarında tam yetkisi yoksa ya da işletmeden
çıkarılırsa anahtar da yazamaz. Kullanmadığın anahtarı Ayarlar > API'den iptal et. Windows'ta curl Türkçe harfleri bozabilir:
gövdeyi UTF-8 bir dosyaya yazıp --data-binary @siparis.json ile gönder.
Kayıt türleri
Anahtarı oluştururken satış gelince ne yapılacağını seçersin; istekte e_belge_gonder ile tek seferlik değişir.
| Tür | Ne olur? | Kimin için |
|---|---|---|
| Fatura kes ve gönder | Fatura onaylanır, Uygun Muhasebe serisinden numara alır, alıcıya göre e-Fatura ya da e-Arşiv olarak GİB'e gider; e-Arşiv müşterinin e-postasına ulaşır. | e-Fatura olan paket |
| Yalnız muhasebeye kaydet | Fatura başka yerde kesildiyse (ör. e-ticaret altyapının kendi e-Arşivi) numarasını
belge_no ile gönderirsin: satış o numarayla deftere yazılır, Uygun Muhasebe göndermez. e-Belge kullanmayan işletmede
belge_no gerekmez; faturaya kendi serinden numara verilir. | Her paket |
İki türde de müşteri kartı, cari bakiye, gelir, tahsilat ve (satırda urun_kodu varsa) stok işlenir.
Fatura kes POST /faturalar
Müşteri vergi numarasıyla, yoksa e-postasıyla, o da yoksa ünvan ve ille bulunur; bulunamazsa yeni müşteri kartı açılır. Canlı anahtarla satış onaylanıp deftere yazılır; kayıt türü "fatura kes ve gönder" ise e-Fatura ya da e-Arşiv olarak gider.
curl https://uygunmuhasebe.com/api/v1/faturalar \
-H "Authorization: Bearer $UYGUN_MUHASEBE_ANAHTARI" \
-H "Content-Type: application/json" \
-d '{
"siparis_no": "1001",
"alici": {
"unvan": "Ayşe Yılmaz",
"vergi_no": "",
"eposta": "ayse@ornek.com",
"telefon": "05xx xxx xx xx",
"adres": "Atatürk Cad. No: 1",
"ilce": "Kadıköy",
"il": "İstanbul"
},
"satirlar": [
{"aciklama": "Seramik kupa", "miktar": 2, "birim": "Adet", "birim_fiyat": "250.00", "kdv_orani": 20},
{"aciklama": "Kargo", "miktar": 1, "birim_fiyat": "50.00", "kdv_orani": 20}
],
"kdv_dahil": false,
"tahsilat_hesabi_id": 3
}'
| Alan | Açıklama |
|---|---|
alici.unvan zorunlu | Şirket ünvanı ya da kişinin adı soyadı (en çok 255 karakter). |
alici.vergi_no | 10 haneli VKN ya da 11 haneli TCKN (yalnız rakam). Boşsa e-Arşiv faturası nihai tüketiciye kesilir. Sen e-Fatura mükellefiysen ve alıcı GİB'de e-Fatura kayıtlı kullanıcısıysa e-Fatura, değilse e-Arşiv gider. |
alici.eposta | e-Arşiv faturası bu adrese gönderilir. |
alici.vergi_dairesi, telefon, adres, ilce, il | Faturada görünür; müşteri kartında eksikse doldurulur. |
alici.kurumsal | true / false. Verilmezse 10 haneli VKN kurumsal, 11 haneli TCKN şahıs sayılır. |
satirlar zorunlu | 1–200 satır. Her satırda aciklama ve birim_fiyat zorunlu; miktar (varsayılan 1, en az 0,001), birim (varsayılan Adet), kdv_orani (0, 1, 10, 20; varsayılan 20),
urun_kodu (Uygun Muhasebe'deki ürünün stok kodu ya da barkodu: satır ürüne bağlanır, stok düşer; böyle bir ürün yoksa istek 400 döner, önce ürünü ekle). |
kdv_dahil | true ise birim fiyatlar KDV dahildir, KDV ayrıştırılır. |
tarih | YYYY-AA-GG. Verilmezse bugün; ileri tarih olamaz. |
siparis_no | Senin sipariş numaran (en çok 50 karakter); faturada görünür, listede bununla ararsın. Aynı sipariş numarasıyla ikinci fatura kesilmez (çift kaydı önleme). |
para_birimi | Yalnız TRY (verilmezse TRY). Başka para birimiyle gelen istek 400 döner,
fatura kesilmez: dövizli faturayı panelden kes. |
notlar | Faturanın not alanı. |
belge_no | Fatura başka yerde kesildiyse numarası (en çok 30 karakter): satış bu numarayla yalnız muhasebeye yazılır, e-Belge gönderilmez. |
e_belge_gonder | true / false: anahtarın kayıt türünü bu istek için değiştirir. e-Belge kullanan işletmede false ise belge_no gerekir. |
tahsilat_hesabi_id | Ödeme alındıysa kasa / banka hesabının kimliği (GET /hesaplar): faturanın tamamı tahsil edilmiş işlenir. |
Yanıt 201, kesilen fatura:
{
"id": 4821,
"belge_no": "EAR2026000000123",
"durum": "onayli",
"tarih": "2026-10-13",
"vade_tarihi": null,
"siparis_no": "1001",
"para_birimi": "TRY",
"ara_toplam": "550.00",
"iskonto_toplam": "0.00",
"kdv_toplam": "110.00",
"genel_toplam": "660.00",
"tahsil_edilen": "660.00",
"kalan": "0.00",
"alici": {"id": 311, "unvan": "Ayşe Yılmaz", "vergi_no": "", "eposta": "ayse@ornek.com"},
"baska_yerde_kesildi": false,
"e_belge": {"tur": "earsiv", "durum": "gonderildi", "durum_aciklamasi": "Gönderildi, işlenmeyi bekliyor",
"belge_no": "EAR2026000000123", "ettn": "…", "alici_yaniti": "", "hata": "",
"gonderim_zamani": "2026-10-13T11:05:12.408211+00:00"},
"goruntuleme_adresi": "https://uygunmuhasebe.com/p/…/",
"pdf_adresi": "https://uygunmuhasebe.com/p/…/fatura.pdf",
"olusturma": "2026-10-13T11:05:11.913542+00:00",
"satirlar": [{"aciklama": "Seramik kupa", "miktar": "2", "birim": "Adet", "birim_fiyat": "250",
"kdv_orani": "20", "kdv": "100.00", "toplam": "600.00"}, …]
}
Tutarlar yuvarlama kaybı olmasın diye metin olarak döner; zamanlar ISO 8601, UTC'dir. e-Belge gönderimi fatura kaydedildikten
hemen sonra yapılır: yanıttaki e_belge null olabilir ya da henüz son durumunu göstermeyebilir; sonucunu
GET /faturalar/{id} ile ya da webhook'la öğrenirsin. goruntuleme_adresi giriş gerektirmeyen fatura sayfasıdır, müşterine
sipariş e-postanda da verebilirsin. pdf_adresi e-Belge GİB'e gittiyse entegratördeki resmî PDF'i açar, PDF henüz hazır değilse
fatura sayfasına yönlendirir. Test anahtarıyla durum taslak olur, adresler boş döner.
Faturaları oku GET /faturalar
GET /faturalar?siparis_no=1001 siparişin faturasını bulur. durum (taslak, onayli, iptal)
ve sayfa ile süzülür; sayfada 50 fatura, en yenisi önce:
{"sonuclar": [{…fatura, satirlar hariç…}], "sayfa": 1, "sonraki_sayfa": 2}
GET /faturalar/{id} tek faturayı satırları ve e-Belge durumuyla döner. e_belge.durum:
| durum | Anlamı |
|---|---|
taslak | Henüz GİB'e gönderilmedi |
kuyrukta | Gönderim sırasında |
gonderildi | Gönderildi, işlenmeyi bekliyor |
basarili | Başarıyla tamamlandı |
hata | Hata |
iptal | İptal edildi |
e_belge.hata durum hata iken nedeni yazar (ör. eksik bilgi); faturayı Uygun Muhasebe'de düzeltip
yeniden gönderirsin. alici_yaniti ticari e-Faturada alıcının yanıtıdır
(bekliyor, kabul, kismi, red); e-Arşivde boştur.
Tahsilat işle POST /faturalar/{id}/tahsilat
Ödemesi sonradan gelen faturaya tahsilat ekler (canlı anahtar). hesap_id zorunlu; tutar verilmezse kalan borcun tamamı,
tarih verilmezse bugün; aciklama isteğe bağlı.
curl https://uygunmuhasebe.com/api/v1/faturalar/4821/tahsilat \
-H "Authorization: Bearer $UYGUN_MUHASEBE_ANAHTARI" -H "Content-Type: application/json" \
-d '{"hesap_id": 3, "tutar": "660.00"}'
{"tahsilat": {"id": 912, "tutar": "660.00", "tarih": "2026-10-13", "hesap_id": 3, "makbuz_no": "MKB2026000000045"},
"fatura": {…}}
GET /hesaplar kasa ve banka hesaplarını listeler: {"sonuclar": [{"id": 3, "ad": "İş Bankası", "tur": "banka", "para_birimi": "TRY"}]}
Ürünler GET · POST /urunler
Faturada urun_kodu gönderip stok düşürmek için ürünün Uygun Muhasebe'de aynı stok koduyla (ya da barkodla) kayıtlı olması gerekir.
GET /urunler?kod=9786050000001 faturadaki aramanın aynısını yapar (önce stok kodu, sonra barkod); kod verilmezse bütün ürünler
sayfa sayfa döner.
curl https://uygunmuhasebe.com/api/v1/urunler \
-H "Authorization: Bearer $UYGUN_MUHASEBE_ANAHTARI" -H "Content-Type: application/json" \
-d '{"ad": "Örnek Roman", "kod": "9786050000001", "satis_fiyati": "250.00", "kdv_orani": 20}'
{"id": 77, "ad": "Örnek Roman", "kod": "9786050000001", "barkod": "", "tur": "urun", "birim": "Adet",
"kdv_orani": "20", "satis_fiyati": "250", "para_birimi": "TRY", "stok_takibi": true}
ad zorunlu; kod, barkod (en çok 50 karakter), tur (urun, hizmet), birim
(Adet, Kg, Lt, Paket…), kdv_orani (0, 1, 10, 20), satis_fiyati (KDV hariç; kdv_dahil: true ile KDV dahil verilir),
stok_takibi (hizmette varsayılan false). Aynı kod ya da barkodla ürün varsa yeni ürün açılmaz: 409 urun_var ve
var olan ürün "urun" alanında döner. Anahtarı oluşturanın ürünlerde tam yetkisi olmalı.
Müşteriler GET · POST /cariler
Fatura keserken müşteri kendiliğinden bulunur ya da açılır; ayrıca sormak istersen GET /cariler?vergi_no=1234567890
(ya da ?eposta=). POST /cariler faturadaki alici ile aynı alanları alır, aynı kuralla bulur ya da açar:
yeni açıldıysa 201, bulunduysa 200.
{"id": 311, "unvan": "Örnek Dağıtım A.Ş.", "tur": "musteri", "kurumsal": true, "vergi_no": "1234567890",
"vergi_dairesi": "Kadıköy", "eposta": "muhasebe@ornek.com", "telefon": "", "adres": "", "ilce": "Kadıköy", "il": "İstanbul"}
Çift kaydı önleme
- Sipariş numarası:
siparis_noile kesilmiş bir fatura varsa aynı numarayla yeni fatura kesilmez; yanıt409 siparis_faturalandiolur ve var olan fatura"fatura"alanında döner, onu kaydedip devam edebilirsin. İptal edilen fatura sayılmaz. Bir siparişi parça parça faturalıyorsan numarayı ayır (ör.1001-1,1001-2). Test anahtarının taslakları canlı anahtarı engellemez. - Idempotency-Key: bağlantı koptuğunda isteği yeniden göndermek güvenli olsun diye
POSTisteklerineIdempotency-Keybaşlığı ekle. Aynı anahtarla gelen ikinci istek işlenmez, ilk yanıtIdempotent-Replayed: truebaşlığıyla aynen döner. Aynı anahtar farklı gövdeyle gelirse409 tekrar_anahtari_kullanildidöner. Anahtarlar 30 gün saklanır; yalnız başarılı yanıtlar saklanır, hatalı isteği düzeltip aynı anahtarla gönderebilirsin.
Hatalar
{"hata": {"kod": "gecersiz_istek", "mesaj": "1. satırda KDV oranı 0, 1, 10 ya da 20 olmalı."}}
| HTTP | kod | Ne zaman |
|---|---|---|
| 400 | gecersiz_istek | Eksik ya da hatalı alan, JSON ya da UTF-8 olmayan gövde; mesaj neyin düzeltileceğini söyler. |
| 401 | anahtar_gecersiz | Anahtar yok, yanlış ya da iptal edilmiş. |
| 403 | yetki_yok | Anahtarı oluşturan kişinin yetkisi yetmiyor. |
| 403 | salt_okunur | Abonelik sona erdi; yalnız okuma çalışır. |
| 403 | modul_kapali | Satış modülü kapalı. |
| 403 | test_anahtari | Test anahtarıyla yapılamayan işlem (tahsilat). |
| 404 | bulunamadi, adres_yok | Kayıt ya da adres yok. |
| 405 | yontem_yok | Adres bu yöntemi kabul etmiyor; Allow başlığı kabul edilenleri söyler. |
| 409 | siparis_faturalandi | Siparişin faturası zaten var; fatura "fatura" alanında döner. |
| 409 | urun_var | Aynı kod ya da barkodla ürün var; ürün "urun" alanında döner. |
| 409 | tekrar_anahtari_kullanildi | Idempotency-Key başka bir istekte kullanıldı. |
| 429 | cok_istek | Dakikalık sınır aşıldı; Retry-After saniye sonra yeniden dene. |
| 500 | sunucu_hatasi | Beklenmeyen hata; ekibimize iletilir. Yeniden göndermeden önce kaydın oluşup oluşmadığına bak. |
Webhook'lar
Ayarlar > API'den bir https adresi ekle; faturanın e-Belge durumu değişince adresine POST gelir:
| Olay | Ne zaman |
|---|---|
fatura.gonderildi | Fatura e-Fatura / e-Arşiv olarak gönderildi |
fatura.hata | Fatura gönderilemedi |
fatura.kabul | Alıcı e-Faturayı kabul etti |
fatura.red | Alıcı e-Faturayı reddetti |
fatura.iptal | Fatura iptal edildi |
deneme | Ayarlar > API'de Deneme gönder'e bastın; veri yalnız bir mesajdır. |
POST /wp-json/uygun-muhasebe/v1/webhook
Content-Type: application/json; charset=utf-8
User-Agent: UygunMuhasebe-Webhook/1.0
X-UygunMuhasebe-Olay: fatura.gonderildi
X-UygunMuhasebe-Teslim: 5531
X-UygunMuhasebe-Zaman: 1791896711
X-UygunMuhasebe-Imza: sha256=6f1c…
{"id": 5531, "olay": "fatura.gonderildi", "olusturma": "2026-10-13T11:05:13.120044+00:00", "veri": {…fatura…}}
veri, GET /faturalar/{id}'nin döndüğü faturadır (olay anındaki son hali). İmzayı doğrula: Ayarlar > API'deki imza
anahtarıyla X-UygunMuhasebe-Zaman + "." + ham gövde metninin HMAC-SHA256'sını hesapla, başlıktakiyle sabit sürede karşılaştır; 5 dakikadan
eski zamanı reddet. Gövdeyi JSON olarak çözüp yeniden yazma: imza gelen baytlar üzerindendir.
# Python
import hashlib, hmac, time
def dogru_mu(gizli: str, zaman: str, govde: bytes, imza_basligi: str) -> bool:
beklenen = hmac.new(gizli.encode(), zaman.encode() + b"." + govde, hashlib.sha256).hexdigest()
return zaman.isdigit() and abs(time.time() - int(zaman)) < 300 and hmac.compare_digest("sha256=" + beklenen, imza_basligi)
// PHP
$govde = file_get_contents('php://input');
$zaman = $_SERVER['HTTP_X_UYGUNMUHASEBE_ZAMAN'] ?? '';
$imza = $_SERVER['HTTP_X_UYGUNMUHASEBE_IMZA'] ?? '';
$beklenen = 'sha256=' . hash_hmac('sha256', $zaman . '.' . $govde, getenv('UYGUN_MUHASEBE_WEBHOOK_GIZLI'));
$dogru = ctype_digit($zaman) && abs(time() - (int) $zaman) < 300 && hash_equals($beklenen, $imza);
- Yanıt: 10 saniye içinde
2xxdön, işini sonra yap.2xxdışındaki her yanıt (404, 500, giriş sayfası…) başarısız sayılır.2xxile bir HTML sayfası dönerse teslim edildi sayılır ama teslim kaydında uyarı çıkar: adres büyük ihtimalle olayı işleyen kod değil, sitenin bir sayfasıdır. - Yeniden deneme: başarısız teslim 15 dakika, 30 dakika, 1, 3, 6 ve 12 saat arayla yeniden denenir (en çok 7 deneme). Aynı olay iki kez
gelebilir:
idile tekrarları ayıkla. - Yönlendirme izlenmez: adresin başka adrese yönlendiriyorsa (ör.
www'suz adrese ya da sonda/olan adrese301) teslim başarısız sayılır. Teslim kaydında yönlendirdiği adres yazar; webhook adresini onunla değiştir. - Adres:
https://ile başlamalı ve herkese açık bir sunucu olmalı: localhost, iç ağ (10.x, 172.16–31.x, 192.168.x) ve bulut meta veri adresleri (169.254.x) kabul edilmez. - Teslim kaydı: Ayarlar > API'de son teslimler, deneme sayısı, yanıt kodu ve sorunun açıklamasıyla görünür. Bağlantıyı kurarken Deneme gönder ile dene: sonuç hemen ekrana gelir.
WordPress / WooCommerce
WooCommerce siparişlerini kendi kurallarınla göndermek istiyorsan (siparişe eklediğin vergi no alanı, kendi fatura kuralların) bu iki parçayla başla; ikisi aynı eklenti dosyasına girer. Kod yazmadan bağlamak için yukarıdaki nota bak.
1. Sipariş tamamlanınca fatura kes. Sipariş "Tamamlandı" olunca WooCommerce'in Action Scheduler'ı faturayı arka planda gönderir.
Sipariş numarası siparis_no olur: sipariş ikinci kez tamamlanırsa yeni fatura kesilmez, 409 ile ilk fatura döner ve eklenti
onu kaydeder. Fatura numarası ve bağlantısı sipariş notuna, kimliği ve PDF adresi siparişin özel alanlarına yazılır; hata olursa nedeni
sipariş notuna düşer. Fatura kesildi ama e-Belge gönderilemediyse (ör. %0 KDV'de muafiyet sebebi) bu da nota yazılır.
<?php
/**
* Plugin Name: Uygun Muhasebe fatura
* Description: Tamamlanan WooCommerce siparişinin faturasını Uygun Muhasebe'de keser.
*
* wp-content/plugins/uygun-muhasebe/uygun-muhasebe.php olarak kaydet, Eklentiler'den etkinleştir.
* wp-config.php'ye anahtarını yaz (Ayarlar > API'de oluşturduğun anahtar):
* define( 'UYGUN_MUHASEBE_ANAHTARI', 'tzk_canli_...' );
*/
if ( ! defined( 'UYGUN_MUHASEBE_API' ) ) {
define( 'UYGUN_MUHASEBE_API', 'https://uygunmuhasebe.com/api/v1' );
}
if ( ! defined( 'UYGUN_MUHASEBE_KDV_ORANI' ) ) {
define( 'UYGUN_MUHASEBE_KDV_ORANI', 20 ); // WooCommerce'te vergi kapalıysa fiyatlar KDV dahil, bu oranla
}
// Ödeme alınınca kesmek istersen: woocommerce_order_status_processing
add_action( 'woocommerce_order_status_completed', 'uygun_muhasebe_siraya_al' );
add_action( 'uygun_muhasebe_fatura_kes', 'uygun_muhasebe_fatura_kes' );
// Siparişi tamamlayan ekran beklemesin: WooCommerce'in Action Scheduler'ı arka planda gönderir.
function uygun_muhasebe_siraya_al( $order_id ) {
if ( function_exists( 'as_enqueue_async_action' ) ) {
as_enqueue_async_action( 'uygun_muhasebe_fatura_kes', array( $order_id ), 'uygun-muhasebe' );
} else {
uygun_muhasebe_fatura_kes( $order_id );
}
}
function uygun_muhasebe_fatura_kes( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order || $order->get_meta( '_uygun_muhasebe_fatura_id' ) ) {
return; // fatura zaten kesildi
}
// Vergi açıksa satırın oranı WooCommerce'in hesapladığı vergiden gelir (vergisiz satır %0);
// kapalıysa fiyatlar KDV dahildir, oranı UYGUN_MUHASEBE_KDV_ORANI.
$vergili = wc_tax_enabled();
$kalemler = $order->get_items( array( 'line_item', 'shipping', 'fee' ) );
// Eksi tutarlı ücret (ör. havale indirimi) satırlara tutarlarıyla orantılı dağıtılır.
$arti = 0;
$eksi = 0;
foreach ( $kalemler as $kalem ) {
$tutar = (float) $kalem->get_total() + (float) $kalem->get_total_tax();
if ( $tutar < 0 ) {
$eksi -= $tutar;
} else {
$arti += $tutar;
}
}
if ( $arti <= $eksi ) {
$order->add_order_note( 'Uygun Muhasebe: siparişin tutarı 0, fatura kesilmedi.' );
return;
}
$carpan = 1 - $eksi / $arti;
$satirlar = array();
foreach ( $kalemler as $kalem ) {
$adet = max( 1, (float) $kalem->get_quantity() );
$net = (float) $kalem->get_total(); // indirim düşülmüş; vergi açıksa KDV hariç
if ( $net <= 0 ) {
continue; // ücretsiz kargo, eksi tutarlı ücret
}
$oran = $vergili ? uygun_muhasebe_oran( (float) $kalem->get_total_tax() / $net * 100 ) : UYGUN_MUHASEBE_KDV_ORANI;
$satirlar[] = array(
'aciklama' => $kalem->get_name(),
'miktar' => $adet,
'birim_fiyat' => number_format( $net * $carpan / $adet, 4, '.', '' ),
'kdv_orani' => $oran,
// Stok da düşsün istersen: 'urun_kodu' => ürünün SKU'su. Ürün Uygun Muhasebe'de aynı stok
// koduyla kayıtlı olmalı (POST /urunler), yoksa istek 400 döner.
);
}
$iller = WC()->countries->get_states( $order->get_billing_country() );
$il = $order->get_billing_state(); // TR34 -> İstanbul
$unvan = $order->get_billing_company();
if ( ! $unvan ) {
$unvan = $order->get_formatted_billing_full_name();
}
$govde = array(
'siparis_no' => (string) $order->get_order_number(), // aynı sipariş ikinci kez faturalanmaz (409)
'para_birimi' => $order->get_currency(), // TRY değilse 400 döner, nedeni sipariş notuna düşer
'kdv_dahil' => ! $vergili,
'alici' => array(
'unvan' => $unvan,
// Fatura eklentinin vergi no alanı; boşsa e-Arşiv nihai tüketiciye kesilir.
'vergi_no' => preg_replace( '/\D/', '', (string) $order->get_meta( '_billing_vergi_no' ) ),
'vergi_dairesi' => (string) $order->get_meta( '_billing_vergi_dairesi' ),
'eposta' => $order->get_billing_email(),
'telefon' => $order->get_billing_phone(),
'adres' => trim( $order->get_billing_address_1() . ' ' . $order->get_billing_address_2() ),
'ilce' => $order->get_billing_city(),
'il' => isset( $iller[ $il ] ) ? $iller[ $il ] : $il,
),
'satirlar' => $satirlar,
);
$yanit = wp_remote_post( UYGUN_MUHASEBE_API . '/faturalar', array(
'timeout' => 30,
'headers' => array(
'Authorization' => 'Bearer ' . UYGUN_MUHASEBE_ANAHTARI,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $govde ),
) );
if ( is_wp_error( $yanit ) ) {
$order->add_order_note( 'Uygun Muhasebe: bağlanılamadı (' . $yanit->get_error_message() . ').' );
return; // siparişi yeniden "Tamamlandı" yapınca tekrar denenir
}
$kod = (int) wp_remote_retrieve_response_code( $yanit );
$veri = json_decode( wp_remote_retrieve_body( $yanit ), true );
if ( 409 === $kod && isset( $veri['fatura'] ) ) {
$veri = $veri['fatura']; // bu sipariş daha önce faturalanmış: o faturayı kaydet
} elseif ( 201 !== $kod ) {
$mesaj = isset( $veri['hata']['mesaj'] ) ? $veri['hata']['mesaj'] : 'yanıt okunamadı';
$order->add_order_note( 'Uygun Muhasebe: fatura kesilemedi (' . $kod . '): ' . $mesaj );
return;
}
if ( 'taslak' === $veri['durum'] ) {
// Test anahtarı: fatura taslak kalır. Sipariş işaretlenmez; canlı anahtarla yeniden kesilir.
$order->add_order_note( 'Uygun Muhasebe test faturası (taslak, id ' . $veri['id'] . '): canlı anahtara '
. 'geçince sipariş yeniden "Tamamlandı" olduğunda gerçek fatura kesilir.' );
return;
}
$order->update_meta_data( '_uygun_muhasebe_fatura_id', $veri['id'] );
$order->update_meta_data( '_uygun_muhasebe_fatura_pdf', $veri['pdf_adresi'] );
$order->save();
$not = 'Uygun Muhasebe faturası: ' . $veri['belge_no'] . ' ' . $veri['goruntuleme_adresi'];
if ( ! empty( $veri['e_belge']['hata'] ) ) { // ör. %0 KDV'de muafiyet sebebi: Uygun Muhasebe'de seçip gönder
$not .= ' — e-Belge gönderilemedi: ' . $veri['e_belge']['hata'];
}
$order->add_order_note( $not );
}
// WooCommerce'in hesapladığı oranı Uygun Muhasebe'nin oranlarından en yakınına yuvarlar (0, 1, 10, 20).
function uygun_muhasebe_oran( $oran ) {
$en_yakin = 20;
foreach ( array( 0, 1, 10, 20 ) as $o ) {
if ( abs( $oran - $o ) < abs( $oran - $en_yakin ) ) {
$en_yakin = $o;
}
}
return $en_yakin;
}
- WooCommerce'te vergi no alanı yoktur: fatura eklentinin alan adını yaz (örnekte
_billing_vergi_no); boşsa fatura nihai tüketiciye e-Arşiv olarak kesilir. - KDV: WooCommerce'te vergi açıksa her satırın oranı WooCommerce'in hesapladığı vergiden gelir; vergisi 0 olan satır (ör. "Sıfır oran"
sınıfındaki ürün) %0 gider, bu yüzden vergiyi açtıysan oranları da gir. Vergi kapalıysa fiyatlar KDV dahil sayılır ve hepsine
UYGUN_MUHASEBE_KDV_ORANIyazılır; farklı oranlı ürünlerin varsa (ör. kitap ve kırtasiye) vergiyi aç. - İndirim: kupon indirimi satır tutarlarında zaten düşülüdür. Havale indirimi gibi eksi tutarlı ücretler satırlara tutarlarıyla orantılı dağıtılır; fatura müşterinin ödediği tutarı verir (satır yuvarlamasından kuruş farkı olabilir).
- Test anahtarı: kesilen taslak siparişi işaretlemez, yalnız sipariş notuna yazılır. Canlı anahtara geçince sipariş yeniden "Tamamlandı" olduğunda gerçek fatura kesilir.
- TL dışındaki para birimiyle verilen sipariş faturalanmaz; nedeni sipariş notuna düşer.
- Sipariş sonradan iptal ya da iade edilirse fatura kendiliğinden iptal edilmez: e-Arşiv'i 8 gün içinde Uygun Muhasebe'de iptal edersin, sonrası için iade faturası kesersin.
2. Webhook'u karşıla. e-Belge durumu değişince (gönderildi, hata, iptal…) siparişe not düşer. Webhook adresi olarak
https://siten.com/wp-json/uygun-muhasebe/v1/webhook yaz.
<?php
/*
* Uygun Muhasebe webhook'unu WordPress'te karşılar (aynı eklenti dosyasının sonuna ekle).
* Webhook adresi: https://siten.com/wp-json/uygun-muhasebe/v1/webhook
* (kalıcı bağlantılar "Düz" ise: https://siten.com/?rest_route=/uygun-muhasebe/v1/webhook)
* wp-config.php'ye webhook'un imza anahtarını yaz (Ayarlar > API > Webhook'lar > Göster):
* define( 'UYGUN_MUHASEBE_WEBHOOK_GIZLI', 'whsec_...' );
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'uygun-muhasebe/v1', '/webhook', array(
'methods' => 'POST',
'callback' => 'uygun_muhasebe_webhook',
'permission_callback' => '__return_true', // isteği imza doğrular
) );
} );
function uygun_muhasebe_webhook( WP_REST_Request $istek ) {
$govde = $istek->get_body(); // ham gövde: imza bunun üzerinden hesaplanır
$zaman = (string) $istek->get_header( 'X-UygunMuhasebe-Zaman' );
$imza = (string) $istek->get_header( 'X-UygunMuhasebe-Imza' );
$beklenen = 'sha256=' . hash_hmac( 'sha256', $zaman . '.' . $govde, UYGUN_MUHASEBE_WEBHOOK_GIZLI );
$taze = preg_match( '/^\d+$/', $zaman ) && abs( time() - (int) $zaman ) <= 300; // 5 dakikadan eski değil
if ( ! $taze || ! hash_equals( $beklenen, $imza ) ) {
return new WP_REST_Response( array( 'hata' => 'imza geçersiz' ), 401 );
}
$olay = json_decode( $govde, true );
if ( get_transient( 'uygun_muhasebe_olay_' . $olay['id'] ) ) {
return new WP_REST_Response( array( 'tamam' => true ), 200 ); // aynı olay ikinci kez geldi
}
set_transient( 'uygun_muhasebe_olay_' . $olay['id'], 1, DAY_IN_SECONDS );
if ( 0 === strpos( $olay['olay'], 'fatura.' ) && ! empty( $olay['veri']['siparis_no'] ) ) {
$no = (string) $olay['veri']['siparis_no'];
$order = wc_get_order( (int) $no );
$ebelge = $olay['veri']['e_belge'];
// Sipariş numarası eklentisi varsa numara kimlikten farklıdır: numarası tutmayan siparişe not yazılmaz.
if ( $order && (string) $order->get_order_number() === $no && $ebelge ) {
$not = 'Uygun Muhasebe ' . $olay['veri']['belge_no'] . ' — ' . $ebelge['durum_aciklamasi'];
$order->add_order_note( $ebelge['hata'] ? $not . ': ' . $ebelge['hata'] : $not );
}
}
return new WP_REST_Response( array( 'tamam' => true ), 200 ); // 10 saniye içinde 2xx dön
}
Sipariş, faturadaki siparis_no ile bulunur. Sipariş numarası eklentisi kullanıyorsan (numara sipariş
kimliğinden farklıysa) not yazılmaz, yanlış siparişe de düşmez; siparişi o eklentinin işleviyle bulacak şekilde değiştir.
Sınırlar
- İşletme başına dakikada 120 istek; bir faturada en çok 200 satır; listelerde sayfada 50 kayıt.
- Faturalar Türk lirasıyla kesilir. İskonto, tevkifat ve ÖTV gibi özel durumlar için faturayı panelden kes; indirimi satır fiyatına yansıt.
- API'den kesilen e-Arşiv faturasına internet satışı bilgileri (satışın yapıldığı site adresi, ödeme şekli ve tarihi, kargo firması) yazılmaz. İnternetten satış yapıyorsan e-Arşiv faturada bu bilgiler istenir (509 sıra no.lu VUK Genel Tebliği): siten WooCommerce, Shopify, ikas ya da IdeaSoft ise Pazaryeri > Mağaza bağla yolunu kullan, orada kendiliğinden yazılır.
- %0 KDV'li satır içeren fatura e-Belge'de muafiyet sebebi ister; API bunu almaz. Böyle bir faturanın e-Belge'si
hatadurumuna düşer (webhook:fatura.hata); Uygun Muhasebe'de faturayı açıp e-Belge bölümünde fatura tipini ve muafiyet sebebini seçerek gönderirsin. - e-Belge göndermek için e-Fatura olan paket (Ön Muhasebe + e-Fatura ya da Sadece e-Fatura), tamamlanmış e-Fatura geçişi ve e-kontör gerekir; e-kontör yoksa fatura onaylanır, e-Belge sonra gönderilir.