# FAZ 3b — Alt Üye Senkronizasyonu ve Kırılım Enjeksiyonu

Bu adımda satıcılar iyzico'ya kaydediliyor ve ödeme isteği pazaryeri sepetine
dönüştürülüyor. Kod hazır; ama eklentinin mevcut ödeme akışında **beş hata**
buldum, ikisi para akışını doğrudan bozuyor.

---

## 1. SDK'da withholdingTax yok

Faz 3a'da stopajı ödeme isteğinde `withholdingTax` alanıyla göndermeyi
planlamıştık. **Bu SDK sürümüyle mümkün değil.**

Kontrol ettim:
- `BasketItem`'da `setWithholdingTax` **yok**
- `getJsonObject()` ve `toPKIRequestString()` bu alanı yazmıyor
- `SubMerchantPaymentItemUpdateRequest` yalnızca `subMerchantKey`,
  `paymentTransactionId`, `subMerchantPrice` alıyor — yani Hakediş Güncelleme
  yolu da kapalı
- SDK'nın tamamında "withholding" kelimesi hiç geçmiyor

Eklentiyle gelen SDK, iyzico'nun bu alanı eklemesinden önceki bir sürüm.

### Bunun paraya etkisi yok

Stopajı `subMerchantPrice`'tan düşüyoruz; satıcıya giden tutar doğru.
`withholdingTax` iyzico tarafında hiçbir hesaplamaya girmiyor, yalnızca para
gönderim raporunda gösterilen bir kolon. Beyanın kaynağı bizim defterimiz ve
muhtasar beyannamemiz.

### Neden alanı SDK'ya elle eklemiyorum

`toPKIRequestString()` metodu **HMAC imzasının** üretiminde kullanılıyor.
Alanı iyzico'nun beklediği sıradan farklı bir yere yazarsak imza doğrulaması
kırılır ve **tüm ödemeler** başarısız olur — bir kırılımın raporu değil, ödeme
sisteminin tamamı. Bu riski rapor kolonu için almam.

Rapor kolonu gerçekten gerekiyorsa doğru yol SDK'yı güncel sürüme çıkarmak.
Ama o da `platform/plugins/fob-iyzipay/src/Iyzipay/` altını değiştirmek demek
ve eklenti güncellemesi geldiğinde çakışır. Şimdilik gerek yok; ihtiyaç
doğarsa ayrıca ele alırız.

---

## 2. Eklentinin ödeme akışındaki hatalar

`platform/plugins/fob-iyzipay/src/Providers/HookServiceProvider.php`
satır 200–285 arası. Ödeme isteğini kuran kod burada.

### Hata 1 — Alıcı posta kodu, değer değil metin gönderiliyor 🔴

```php
$buyer->setZipCode(is_plugin_active('ecommerce') ? 'zip_code' : 'zip');
```

Bu, iyzico'ya alıcının posta kodu olarak **`"zip_code"` metnini** gönderiyor.
Hemen altındaki `$shippingAddress` bloğu aynı şeyi doğru yapıyor:

```php
$shippingAddress->setZipCode($paymentData['address'][... 'zip_code']);
```

Yani yazarken kopyalanmış ama içi doldurulmamış. Fraud skorunu etkileyebilir,
bazı bankalarda 3D doğrulamada sorun çıkarır.

### Hata 2 — Alıcı kimlik numarası rastgele üretiliyor 🔴

```php
$buyer->setIdentityNumber(Str::random(5));
```

Beş karakterlik rastgele bir dize gönderiliyor. iyzico'nun alt üye dokümanı
şunu açıkça yazıyor: iletilen tüm parametreler gerçek değerleri içermeli,
rastgele veya gerçek olmayan bilgiler iletilmemeli. Alt üye için yazılmış bir
uyarı ama alıcı tarafında da fraud kontrollerini tetikleyen bir kalem.

Kapıda ödeme ve alışveriş kredisi gibi ürünlere geçtiğinizde TCKN zorunlu
hale gelir; o noktada bu satır işi tamamen bloke eder.

### Hata 3 — Sepet kalemi kimliği tekil değil ⚠

```php
$basketItem->setId($product->product_id);
```

Aynı ürün iki farklı varyantla sepette olduğunda iki kalem **aynı** kimliği
taşıyor. iyzico dönüşünde `paymentTransactionId` hangi satıra ait, anlaşılamıyor.

Bizim modelde bu doğrudan para hatasına dönüşür: onay yanlış kırılıma
gidebilir. Kodda `order_product_id` kullanıyorum, o tekil.

### Hata 4 — Kategoride off-by-one ⚠

```php
if (count($product->product->categories) > 1) {
    $basketItem->setCategory1($product->product->categories[0]->name);
} else {
    $basketItem->setCategory1('-');
}
```

Tek kategorili ürünlerde `count == 1`, koşul sağlanmıyor, kategori `'-'` oluyor.
Oysa `categories[0]` orada duruyor. Kontrol `>= 1` olmalıydı. İkinci kategori
kontrolü de aynı hatayı taşıyor (`> 2` yerine `>= 2`).

Etkisi: iyzico'nun kategori bazlı raporlaması ve fraud modeli boş veri alıyor.

### Hata 5 — Kargo bedeli sepette görünmüyor ⚠

```php
$paymentRequest->setPrice($subTotal);        // ürün bedelleri toplamı
$paymentRequest->setPaidPrice($paymentData['amount']);  // kargo + vergi dahil
```

`price` ile `paidPrice` arasındaki fark kargo ve diğer kalemler. Tek satıcılı
kurulumda sorun değil. Ama pazaryerinde `subMerchantPrice` toplamının `price`
ile ilişkisi kritik: aradaki fark pazaryerinde kalıyor. Kargo bedelini
satıcıya yansıtacaksak (Faz 2, B5) bunu kırılım hesabına açıkça yazmalıyız,
yoksa kargo sessizce pazaryerinde kalır ve satıcı hesabı tutmaz.

`MarketplaceBasketBuilder` şu an kargoyu kırılım seviyesinde sıfır bırakıyor
ve sipariş seviyesinde ele alıyor. Kargo dağıtım kuralı netleşince
(desi bazlı mı, kalem sayısına göre mi) buraya bağlanacak — bu, senin
onayını bekleyen bir karar.

---

## 3. IBAN doğrulaması neden şimdi

iyzico'nun şartı: IBAN, bireysel satıcıda `contactName`+`contactSurname` ile,
şirkette `legalCompanyTitle` ile uyumlu olmalı. Ve IBAN boş bırakılırsa alt üye
kaydı **başarılı oluyor**, hata ancak ürün onayı anında çıkıyor.

Yani yanlış IBAN'ı kayıt gününde değil, **satıcıya para göndermeye çalıştığın
gün** öğreniyorsun. Araya haftalar giriyor ve o arada satışlar birikiyor.

`App\Magostore\Support\Iban` bunu kayıt anına çekiyor:

- **mod-97 kontrolü** (ISO 13616). Test ettim: gerçek TR IBAN geçiyor, son
  basamağı bozulmuş hali geçmiyor, 25 haneli geçmiyor, Alman IBAN'ı geçmiyor.
- **Hesap sahibi adı karşılaştırması.** Kesin eşleşme aramıyor çünkü mağaza adı
  "Acme Ticaret" iken yasal unvan "ACME TİCARET LİMİTED ŞİRKETİ" olabilir ve bu
  normaldir. Türkçe karakterleri normalize edip şirket türü eklerini
  (`limited şirketi`, `a.ş.`, `ticaret`, `sanayi`, `ithalat`…) atıyor, sonra
  anlamlı kelimelerin yarısı ortaksa kabul ediyor.

Test sonuçları:

| Hesap sahibi | Mağaza adı | Sonuç |
|---|---|---|
| ACME TİCARET LİMİTED ŞİRKETİ | Acme Ticaret | eşleşti |
| Mehmet Yılmaz | Mehmet Yılmaz Butik | eşleşti |
| ÖZKAN SANAYİ VE TİCARET A.Ş. | Özkan Store | eşleşti |
| Ayşe Demir | Zeynep Kaya Tekstil | **eşleşmedi** |

Son satır tam olarak yakalamak istediğimiz durum: satıcının başkasının
IBAN'ını girmesi.

Not: uyumsuzlukta işlemi **durdurmuyorum**, uyarı logluyorum. Ad karşılaştırması
sezgisel bir kontrol; yanlış pozitifle gerçek satıcıyı engellemek, şüpheli bir
IBAN'ı manuel incelemeye düşürmekten daha kötü. Kesin karar iyzico'nun kendi
kontrolünde.

---

## 4. Komisyon oranı neden ayrı tabloda

`CommissionRateResolver` üç kademeli çalışıyor: mağazaya özel oran → kategori
oranı (`mp_category_sale_commissions`) → varsayılan.

Mağazaya özel oran için yeni tablo açtım (`mg_store_commission_overrides`),
çünkü oranı `mp_stores` üzerinde tek kolonda tutmak geçmişi kaybetmek demek.
Aracılık sözleşmesinde satıcı aleyhine geçmişe yönelik değişiklik haksız
ticari uygulama sayıldığı için, hangi siparişin hangi oranla hesaplandığını
**kanıtlayabilir** olmak zorundayız. Tablo, oranın hangi tarihten itibaren ve
hangi sözleşme versiyonuna dayanarak geçerli olduğunu tutuyor.

Bir tasarım tercihi: ürün birden fazla kategorideyse **en yüksek** oranı
alıyorum. En doğrusu en derin (en spesifik) kategorinin oranı olurdu ama
kategori ağacı derinliği hesabı henüz yok. En yükseği almak, pazaryeri
aleyhine sapma bırakmıyor — satıcı lehine sapma isterseniz `max` yerine `min`
yeterli, tek satır.

---

## 5. Teslim edilen kod

| Dosya | Görevi |
|---|---|
| `app/Magostore/Psp/StoreSubMerchantSynchronizer.php` | Mağaza → iyzico alt üye, tip bazlı alan doğrulaması, GSM normalizasyonu, TCKN/IBAN maskeli denetim izi |
| `app/Magostore/Support/Iban.php` | mod-97 doğrulama, hesap sahibi eşleştirme, maskeleme |
| `app/Magostore/Settlement/MarketplaceBasketBuilder.php` | Kırılımlı sepet kurulumu, eklenti hatalarının düzeltilmiş hali |
| `app/Magostore/Settlement/CommissionRateResolver.php` | Üç kademeli komisyon oranı |
| `app/Console/Commands/SyncSubMerchants.php` | Toplu senkronizasyon, başarısızları yeniden deneme |
| `database/migrations/..._000004_create_store_commission_overrides.php` | Tarih aralıklı mağaza komisyonu |

### Cron

```
30 8 * * *   php /home/KULLANICI/magostore/artisan magostore:sync-submerchants
0  9 * * 1-5 php /home/KULLANICI/magostore/artisan magostore:approve-due
```

Senkronizasyon onaydan önce çalışmalı — alt üye kaydı olmayan mağazanın
kırılımı onaylanamaz.

---

## 6. Sıradaki iş

1. **`HookServiceProvider` devralma** — eklentinin ödeme kurulumunu kendi
   sağlayıcımızla değiştirmek. Eklenti dosyasını doğrudan düzenlemiyoruz;
   güncelleme geldiğinde kaybolur. Botble'ın hook sistemi üzerinden aynı
   noktaya daha yüksek öncelikle bağlanacağız.
2. **`paymentTransactionId` yakalama** — ödeme dönüşünde `getPaymentItems()`
   üzerinden `mg_settlement_items` satırlarını doldurmak. `getItemId()`
   bizim `order_product_id` değerini döndürecek, eşleştirme oradan.
3. **Teslim tetikleyicisi** — sipariş teslim edildiğinde `approvable_at`
   hesabı. İş günü takvimi gerekiyor: resmî tatiller, hafta sonu, arife
   yarım günleri. Bunu doğru kurmak 5 iş günü sınırının doğru sayılması demek.
4. **İade akışı** — onay öncesi `disapprove`, onay sonrası ters kayıt + borç.

2. madde 1'e bağlı. 3 bağımsız ve şimdi başlanabilir.

---

## Onayını bekleyen tek karar

**Kargo bedeli kırılıma nasıl dağıtılacak?** Üç seçenek:

- Sipariş kargosunu kalem sayısına eşit bölmek — basit, ama ağır ürün hafif
  ürünü sübvanse ediyor
- Desi ağırlığına göre dağıtmak — doğru olan, ama her ürünün desi verisi dolu
  olmak zorunda
- Kargoyu kırılıma hiç yazmayıp sipariş seviyesinde satıcıya faturalamak —
  muhasebe olarak en temiz, ama satıcı kırılım raporunda kargoyu görmüyor

Desi bazlı olanı öneriyorum, çünkü Faz 2'de kargoyu satıcıya yansıtma kararı
verdik ve adil dağıtım o. Ama ürün kataloğundaki desi doluluk oranını
bilmiyorum. Onu söylersen buna göre bağlarım.
