# FAZ 3a — REVİZE: iyzico Pazaryeri (Alt Üye İşyeri) Modeli

`03-FAZ3a-para-mimarisi.md` dokümanını bu doküman **kısmen geçersiz kılıyor.**
Defter mimarisi aynı kalıyor; para akışı ve ödeme mekaniği değişiyor.

Kaynak: `docs.iyzico.com/urunler/pazaryeri/pazaryeri-entegrasyonu` ve alt sayfaları.

---

## 1. Üç yapısal düzeltme

### Düzeltme 1 — Havuz bizde değil, iyzico'da

iyzico dokümanının ifadesiyle: ödeme geçtikten sonra para, üye işyeri ilgili
kırılıma onay verene dek **korumalı havuz hesapta** bekletiliyor.

Yani "kendi havuzumuzda tutacağız" kararı, temsilci modelinde şu şekilde
gerçekleşiyor: parayı fiziksel olarak biz tutmuyoruz, **serbest bırakma yetkisini**
biz tutuyoruz. Kontrol bizde, kasa iyzico'da. Hukuki olarak da rahat taraf bu.

`1000` hesabının adı bu yüzden **"iyzico Korumalı Havuz — Onay Bekleyen"** oldu.

### Düzeltme 2 — Ödeme, banka dosyası değil onay çağrısı

Satıcıya para `/payment/iyzipos/item/approve` çağrısıyla gidiyor. Banka toplu
ödeme dosyası üretmiyoruz; o tasarımı iptal ettim.

**Ve en kritik nokta:** iyzico dokümanı açıkça söylüyor — ürünlere onay verme
iyzico tarafından yapılamıyor, kontrol panelinde böyle bir fonksiyon yok, onaylar
**kod seviyesinde** gönderilmek zorunda.

> Pratik anlamı: `magostore:approve-due` komutu çalışmazsa **satıcılar hiç para
> almaz.** Manuel telafi yolu yok. Bu komut ve cron'u, sistemin en kritik tek
> parçası. İzlemesi olmadan canlıya çıkılmamalı.

### Düzeltme 3 — Granülarite sipariş değil, ödeme kırılımı

Onay `paymentTransactionId` bazında veriliyor ve bu değer sepetteki her ürün
satırına karşılık geliyor. Bu yüzden `mg_order_settlements` (sipariş+mağaza)
yetmiyor; altına **`mg_settlement_items`** tablosunu ekledim. Onay motoru bu
tablo üzerinden yürüyor.

---

## 2. Stopaj: iyzico hesaplamıyor, biz hesaplıyoruz

Dokümanın uyarısı net: gönderilen parametreler iyzico tarafından herhangi bir
hesaplamaya tabi tutulmuyor. `withholdingTax` alanı **yalnızca para gönderim
raporunda görünmesi için** kaydediliyor.

Yani stopajı hesaplamak, `subMerchantPrice`'tan düşmek ve doğru tutarı beyan
etmek tamamen bizim sorumluluğumuzda. Faz 2'deki tespit aynen geçerli.

İki yöntem var, ikisi de destekli:

| Yöntem | Ne zaman | Bizim seçimimiz |
|---|---|---|
| Ödeme isteğinde `withholdingTax` gönder | Ödeme anında | ✅ Bunu kullanıyoruz |
| Hakediş Güncelleme servisi ile sonradan | Ödeme sonrası düzeltme | Yalnızca hata düzeltmede |

Ödeme anını seçtim çünkü stopaj matrahı yalnızca satış fiyatı ve KDV oranına
bağlı — ikisi de ödeme anında biliniyor. Teslim tarihini beklemeye gerek yok.
`mg_order_settlements.withholding_method` alanı hangi yolun kullanıldığını tutuyor.

### ⚠ iyzico dokümanındaki örnek bizim formülle uyuşmuyor

Dokümanın hesaplama örneği: 100 TL ürün → "KDV ve Vergiler Hariç Ürün Bedeli:
80₺" → stopaj 0,80 TL.

Ama %20 KDV'li 100 TL'den KDV'yi çıkarmanın doğru yolu `100 / 1,20 = 83,33`.
80 TL'ye ulaşmanın tek yolu `100 × (1 − 0,20)` — ki bu KDV ayırmanın hatalı
yöntemi — ya da %25 KDV varsaymak, ki Türkiye'de %25 oran yok.

Test ettim, farkın büyüklüğü şu:

| Yöntem | 1.000 TL satışta stopaj |
|---|---|
| `gross / (1 + kdv)` — doğru olan | 8,33 TL |
| `gross × (1 − kdv)` — dokümanla eşleşen | 8,00 TL |

Satış başına 0,33 TL, oransal olarak **%4,1 fark.** 100.000 siparişte 33.000 TL
eksik beyan demek.

Kodda doğru formülü kullanıyorum (`gross / (1 + kdv)`). `withholdingTax` iyzico
tarafında hiçbir hesaplamaya girmediği için bu tercih ödeme akışını bozmuyor,
yalnızca rapordaki tutarı etkiliyor. Ama **beyan edilen stopaj tutarı bu, o
yüzden mali müşavirine ve entegrasyon@iyzico.com'a teyit ettir.** Dokümandaki
örneğin yuvarlak sayılarla yazılmış bir illüstrasyon olduğunu düşünüyorum, ama
tahminle geçilecek bir kalem değil.

---

## 3. Revize para akışı

### Adım 1 — Ödeme alındı (kırılımlı)

1.000 TL sipariş, tek satıcı, KDV %20, komisyon %15, kargo 45 TL:

```
                                              BORÇ        ALACAK
1000  iyzico Korumalı Havuz               1.000,00
2000  Satıcı Alacağı :42                                  796,67
4000  Komisyon Geliri                                     125,00
4010  Komisyon KDV'si                                      25,00
4100  Kargo Geliri                                         45,00
2100  Stopaj Borcu                                          8,33
                                          ─────────    ─────────
                                          1.000,00     1.000,00
```

iyzico'ya giden `paymentItem`:
```json
{
  "id": "42-1001",
  "price": "1000.00",
  "subMerchantKey": "<mağazanın anahtarı>",
  "subMerchantPrice": "796.67",
  "withholdingTax": "8.33"
}
```

### Adım 2 — Teslim edildi, vade işlendi

Fiş yok. Sadece `mg_settlement_items.approval_status = 'approvable'` ve
`approvable_at = teslim + 3 iş günü` yazılıyor.

### Adım 3 — Onay verildi (`magostore:approve-due`)

```
                                              BORÇ        ALACAK
2000  Satıcı Alacağı :42                    796,67
1000  iyzico Korumalı Havuz                                796,67
```

Satıcının alacağı kapandı, para havuzdan çıktı. iyzico transferi tamamlayınca
mutabakat dosyasıyla `1002` üzerinden eşleşecek.

### Adım 4 — İade, onaydan ÖNCE gelirse

En kolay senaryo. `disapprove` çağrılır, kırılım iade edilir, hakediş fişi ters
kayıtla iptal edilir. Satıcı hiç para almadığı için borç doğmuyor.

### Adım 5 — İade, onaydan SONRA gelirse

Faz 2'de bulduğumuz asıl problem. 5 iş günü kuralı yüzünden çoğu iade bu tarafa
düşecek. `2000` hesabı eksiye geçer, borç oluşur, sonraki hakedişten mahsup edilir.

**Ve mevcut Botble kodu bunu yapamıyor.** `HookServiceProvider::afterOrderRefunded`
şu koşulla çalışıyor:

```php
if ($vendor->balance > $refundAmount) {
    $vendorInfo->balance -= $refundAmount;
```

Yani satıcının bakiyesi iade tutarından **büyük değilse iade hiç kaydedilmiyor** —
para defterden sessizce kayboluyor. Üstelik `beforeOrderRefund` kontrolü
`$vendorInfo->balance` üzerinde, `afterOrderRefunded` ise `$vendor->balance`
üzerinde; iki farklı nesne, ve `>=` yerine `>` kullanılmış. Bakiyenin iade
tutarına tam eşit olduğu durumda da sessizce hiçbir şey olmuyor.

Bizim modelde satıcı zaten ödenmiş olacağı için bakiye çoğu zaman iade tutarından
küçük — yani bu kod yolu **normal senaryoda bozuk.** Ters kayıt + borç bakiyesi
yaklaşımı bu yüzden zorunlu, tercih değil.

---

## 4. Alt üye işyeri kaydı

Satıcılar iyzico'ya bir kez kaydedilmeli, dönen `subMerchantKey` saklanmalı.
Üç tip var ve zorunlu alanlar tipe göre değişiyor:

| Tip | Zorunlu ek alanlar |
|---|---|
| `PERSONAL` (bireysel) | `contactName`, `contactSurname`, `identityNumber` (TCKN) |
| `PRIVATE_COMPANY` (şahıs) | `taxOffice`, `legalCompanyTitle` |
| `LIMITED_OR_JOINT_STOCK_COMPANY` | `taxOffice`, `taxNumber`, `legalCompanyTitle` |

Bu, senin özellik listendeki "bireysel/kurumsal satıcı" ayrımının teknik karşılığı.

**Mevcut `mp_stores` tablosu bu alanları taşımıyor:** vergi dairesi yok, TCKN yok,
alt üye tipi yok, IBAN yok (`mp_vendor_info.bank_info` serbest metin). Bu yüzden
`mg_store_psp_accounts` tablosunu ekledim.

### İki sessiz tuzak

**IBAN uyumu.** Doküman şart koyuyor: IBAN, bireysel satıcıda
`contactName`+`contactSurname` ile, şirkette `legalCompanyTitle` ile **uyumlu**
olmalı. Satıcı başkasının IBAN'ını giremiyor. Kayıt formunda bu doğrulanmalı,
yoksa hata onay anında çıkar — yani parayı ödemeye çalıştığın gün.

**IBAN'sız alt üye.** Alt üye oluşturulurken IBAN gönderilmezse kayıt başarılı
oluyor, ama para gönderimi için **ürün onayı öncesi mutlaka doldurulmalı.**
`magostore:approve-due` komutu bu yüzden onay göndermeden önce IBAN kontrolü
yapıyor ve eksikse atlayıp uyarı basıyor.

---

## 5. Teslim edilen kod

| Dosya | Görevi |
|---|---|
| `2026_07_24_000002_adapt_ledger_for_agent_model.php` | Temsilci modeli + fon bütünlüğü kontrolü |
| `2026_07_24_000003_align_with_iyzico_marketplace.php` | Alt üye, kırılım, mutabakat tabloları |
| `app/Magostore/Psp/IyzicoMarketplaceClient.php` | Alt üye oluştur/güncelle, onay ver/kaldır |
| `app/Magostore/Settlement/ItemSettlementCalculator.php` | Kırılım hakedişi + stopaj |
| `app/Magostore/Settlement/ItemSettlement.php` | Hakediş değer nesnesi + `paymentItem` üretimi |
| `app/Magostore/Ledger/LedgerEntryDraft.php` | Fiş taslağı, denge doğrulaması |
| `app/Console/Commands/ApproveDueSettlements.php` | **Onay motoru — en kritik parça** |

### iyzico SDK namespace'i hakkında

Eklenti SDK'yı kendi altına taşımış. Sınıflar `Iyzipay\Model\...` değil,
**`FriendsOfBotble\Iyzipay\Iyzipay\Model\...`** altında. Kodda doğru namespace
kullanıldı; iyzico'nun kendi örneklerinden kopyalarken bu tuzağa dikkat.

### Cron

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

Önce kuru çalıştır:
```bash
php artisan magostore:approve-due --dry-run
```

Komut, 5 iş günü sınırını aşmış kırılım bulursa `critical` seviyede log basıyor.
Bu logu bir alarma bağla — sessizce geçmesi gereken bir şey değil.

---

## 6. Sıradaki iş

1. **Alt üye senkronizasyon işi** — satıcı onaylandığında `createSubMerchant`,
   IBAN/unvan değişiminde `updateSubMerchant`
2. **Ödeme akışına kırılım enjeksiyonu** — `fob-iyzipay` ödeme isteğine
   `paymentItem` dizisini ekleyen hook; `paymentTransactionId`'leri yakala ve
   `mg_settlement_items`'a yaz
3. **Teslim tetikleyicisi** — sipariş teslim edildiğinde `approvable_at` hesabı
   (iş günü takvimi, resmî tatiller dahil)
4. **İade akışı** — onay öncesi `disapprove`, onay sonrası ters kayıt + borç
5. **Mutabakat okuyucu** — iyzico mutabakat dosyasını `mg_psp_settlement_rows`'a
   aktar, `1002` hesabını kapat

2. madde 1'e bağlı, 3 ve 4 bağımsız gidebilir.
