Public API v1 sözleşmesi Gönderiler PA-19
Gönderi onayı
POST/api/v1/shipments/{id}/confirm
Confirm sırasının adım adım mimari gerekçesi (önce charge, sonra taşıyıcı, ardından kalıcılaştırma) 06-architecture "İki fazlı gönderi yaşam döngüsü" bölümündedir; uygulama TASK-246 ile indi.
Amaç
DRAFT gönderiyi seçilen taşıyıcıyla onaylamak (Kargonomi POST /confirm-shipping-price eşleniği). SENKRONDUR (DEC-186/1): istek içinde ücret kesilir (strict-prepaid, DEC-043 sırası AYNEN: bakiye kontrolü + charge taşıyıcı çağrısından ÖNCE, atomik+idempotent, immutable snapshot), taşıyıcı siparişi oluşturulur ve başarıda gönderi CREATED durumuyla döner. Kalıcı bir READY durumu yoktur.
Yetki
Bearer anahtar; policy confirm yeteneği (RISK-008: PARA KESEN uç view ile yetkilenmez; PA-05 cancel / PA-06 label kalıbı). Başka tenant'ın kaydı 404 (IDOR).
Başlık
Idempotency-Key ZORUNLU (PA-03'teki sözleşmeyle aynı; 5xx yanıt saklanmaz; belirsiz sonuç aynı anahtarla yeniden denenebilir). "İşlem sürüyor" 409 yanıtları da saklanmaz: istemci her zaman AYNI anahtarla yeniden dener.
İstek gövdesi
carrier_code (string, ZORUNLU); kayıtlı bir taşıyıcı kodu ya da özel değer auto (en ucuz teklifi SUNUCU taze hesaplar; eşitlik kırıcısı: önce düşük fiyat, sonra alfabetik kod; DEC-186/4; PA-18 meta.cheapest_carrier_code o anın GÖSTERGESİDİR, seçim de ücret de confirm anının CARİ politikasından çıkar). Alan yokken otomatik seçim YOKTUR: 422 errors.carrier_code. account_model (string, opsiyonel; dealer ya da own_account) seçilen adayın HESAP MODELİDİR (DEC-365). Aday kimliği tek bir kod değil (carrier_code, account_model) çiftidir: aynı taşıyıcı iki modelde de aday olabilir ve PA-18 onları İKİ AYRI satır olarak listeler. Alan gelmezse dealer okunur, yani alanı hiç göndermeyen çağıranın davranışı DEĞİŞMEZ. own_account seçildiğinde gönderi tenant'ın KENDİ doğrulanmış taşıyıcı hesabıyla çıkar ve Sevkora ücret ALMAZ (DEC-363). auto ile own_account birlikte gönderilemez: auto en ucuz TEKLİFİ seçer, kendi-hesap adayı ise fiyat taşımaz. Tanımsız değer 422 errors.account_model.
Davranış (sıra)
durum kapısı (yalnız DRAFT onaylanır; hiçbir yan etki yok). Kapı DRAFT dışını görürse İKİ AYRI cevap verir (TASK-490; DEC-470 A3): CREATED ise gönderi ZATEN vardır ve 200 + detay gövdesi + meta.confirm.outcome="already_created" döner: taşıyıcıya gidilmez, ücret kesilmez; diğer her durum (örn. CANCELLED) eskisi gibi 422 alır. KIRICI DEĞİŞİKLİK: CREATED için bu uç eskiden 422 {message} döndürüyordu. Gerekçesi ölçüldü: istek yolu kenarda kesildiğinde sunucu işi tamamlar, kullanıcı başarılı bir gönderiyi başarısız sanıp tekrar dener ve ürün ona VAR OLAN gönderisini göstermek yerine "onaylanamaz" derdi (14-operations "Taşıyıcı HTTP zaman aşımları", üçüncü eşitsizlik). Kapıdan sonra taslak üzerinde bir onay talebi alınır: aynı taslakta başka bir onay sürerken gelen istek 409 "işlem sürüyor" alır, ücret kesilmez ve taşıyıcıya istek atılmaz. Talep sürdükçe taslak düzenlenemez, dolayısıyla taşıyıcıya giden bilgi, kesilen ücret ve kayıt aynı ana aittir. Ardından gönderici bağı (DEC-188: taslağın warehouse_id bağı kopuksa fail-closed 422), ardından taşıyıcı çözümü (auto dahil; adlandırılmış kod teklif kümesi kelepçelerinden geçer, DEC-190), ardından sınıf seviyesi capability guard (createShipment), ardından HESAP çözümü (bayi modelinde sistem satırı, TASK-243 tek kapısı AYNEN; kendi-hesap modelinde tenant'ın doğrulanmış aktif satırı; iki durumda da satır yoksa ya da belirsizse fail-closed 422 ve ücret kesilmez), ardından ÜCRET (yetersiz bakiye/fiyatlanamaz ise 422, taşıyıcı siparişi YOK; kendi-hesap modelinde bu adım HİÇ koşmaz), ardından niyet kaydı + taşıyıcı createShipment, başarıda tek transaction: CREATED + seçilen hesap satırına bağ + barcode/tracking + olay. Taşıyıcı hatasında BU istekte kesilen ücret geri alınır (net-sıfır), gönderi DRAFT kalır, yeniden denenebilir.
Yeniden deneme
başarısız/yarım kalan confirm sonrası aynı taslak yeniden onaylanabilir. Önceki denemeden AYAKTA kalan ücret (charge'dan sonra düşüş penceresi) AYNI taşıyıcıyla yeni kesim YAPILMADAN kullanılır; FARKLI taşıyıcıya geçiş, ayakta ücret tazeyken 409 "işlem sürüyor" alır (ilk istek taşıyıcı fazında olabilir) ve claim penceresi (varsayılan 600 sn) aşılınca açılır: eski ücret net-sıfır kapatılır, seçilen taşıyıcı için taze kesilir. Hatayla biten bir onay talebini hemen bırakır, yani taslak hemen yeniden onaylanabilir. Süreci yarıda kesilen bir onayın talebi ise aynı pencere dolunca kendiliğinden düşer; o süre içinde gelen yeni onay 409 "işlem sürüyor" alır. Bu bölümdeki "işlem sürüyor" 409'larının hiçbiri saklanmaz: istemci bekler ve AYNI Idempotency-Key ile yeniden dener; istek, talep bırakıldığında ya da pencere dolduğunda ilerler. Süreci yarıda kesilen isteğin kendi anahtarı da pencere dolunca aynı anahtarla yeniden koşar: yanıtı hiç yazılamamış kayıt terk edilmiş sayılır ve aynı anda iki deneme gelirse yalnız biri ilerler. Aynı anahtarla yeniden deneme ikinci bir kesim üretmez: önceki onay tamamlandıysa durum kapısı var olan gönderiyi döndürür (200, meta.confirm.outcome="already_created"), ayakta ücret aynı taşıyıcıyla yeniden kullanılır, farklı taşıyıcı ise pencere dolana dek yine 409 alır.
Başarı 200
detay gövdesi ({data, meta.carrier_capabilities}; status=CREATED) + meta.charge {fee_minor, vat_rate_percent, vat_minor, total_minor, currency, policy_version} (PA-06'daki charge zarfıyla aynı biçim; kesilen ücret yanıtta görünür). Cüzdandan düşen tutar total_minordır; fee_minor NET kalır (TASK-448; REQ-026). Biçim ayniyeti bir NİYET değil bir MEKANİZMADIR: üç yüzey de zarfı tek üreticiden alır. Sonraki etiket isteği (PA-06) ayakta duran bu charge'ı YENİDEN KULLANIR, ikinci kesim olmaz. Kendi-hesap modelinde meta.charge null döner: anahtar HER ZAMAN vardır, değeri yoktur. Anahtarın düşürülmesiyle null dönmesi aynı şey DEĞİLDİR; ikincisi "bu gönderiden ücret alınmadı" der. meta.confirm {outcome} ADİTİF alandır (TASK-490): confirmed bu isteğin bir taşıyıcı siparişi kurup ücret kestiğini, already_created ise bu istekte HİÇBİR ŞEY yapılmadığını söyler. İki sonuç aynı gövdeyi ve aynı 200'ü taşır ama aynı şey DEĞİLDİR; ayrımı statüye değil gövdeye koymak mevcut tüketicileri kırmaz (DEC-297 kalıbı).
Hatalar
401, 404, 409 {message} (Idempotency-Key çakışması: aynı anahtar farklı bir gövdeyle kullanılamaz, yeni istek yeni anahtar ister; ya da "işlem sürüyor": aynı anahtarla ilk istek hâlâ sürüyor, başka bir onay aynı taslağı yürütüyor ya da farklı taşıyıcının ayakta ücreti hâlâ taze. İşlem sürüyor yanıtlarında istek taşıyıcıya HİÇ gitmez ve ücret kesilmez; yanıt saklanmaz, istemci taze durumu okuyup AYNI anahtarla yeniden dener), 422 errors.carrier_code (alan yok; bilinmeyen kod; createShipment desteklemiyor; istenen modelle aday kümesinde yok; auto ile own_account çelişkisi; kendi-hesap satırı doğrulanmamış, devre dışı ya da birden çok), 422 errors.account_model (tanımsız değer), 422 {message} (durum onaylanamaz, CREATED HARİÇ; bkz. yukarıdaki durum kapısı; gönderici bağı kopuk; yetersiz bakiye; fiyatlanamaz; sistem hesabı yapılandırılmamış; taşıyıcı reddi), 429, 502 {message, code}. 502'nin code alanı SEBEBİ ayırır ve üç değer alır: carrier_credential_rejected (taşıyıcıya ulaşıldı, erişim reddedildi; yeniden denemek yardımcı OLMAZ; DEC-297), carrier_response_unrecognized (taşıyıcı yanıt verdi, yanıt kullanılamadı; DEC-464) ve carrier_outcome_unknown (taşıyıcıdan yanıt ALINAMADI; TASK-490, DEC-470 A2). Sonuncusu ONAY UCUNA ÖZGÜDÜR ve gerekçesi şudur: onay bir YAZMA çağrısıdır, yanıt alınamadığında siparişin taşıyıcıda oluşup oluşmadığı BİLİNMEZ. Mesaj bunu adıyla söyler: Sevkora kaydı değişmemiştir (taslak durur) ama taşıyıcıdaki akıbet doğrulanamamıştır; bu istekte kesilen ücret net-sıfır geri alınmıştır ve yeniden deneme ikinci bir sipariş ÜRETMEZ, çünkü taşıyıcıya aynı referans (delivery_no) gider. OKUMA uçlarında (PA-06 etiket, takip) aynı sınıf eskisi gibi carrier_unavailable döner: orada bilinmeyen bir sonuç yoktur.
curl -X POST "{{base_url}}/shipments/01K1GNDR000000000000000001/confirm" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SEVKORA_API_ANAHTARINIZ" \
-H "Idempotency-Key: 7d1f0a3e-5b7c-4a4e-9a44-2f6d1e8b9c01" \
-d '{"carrier_code": "auto"}'