# Servis Sağlayıcı Modülü Yazma

https://dev.wisecp.com/tr/servis-saglayici-modulu-yazma

Servis sağlayıcı modülü bir alan adı sağlayıcısını otomasyona bağlar. Adı tescil eder, transfer eder, yeniler ve müşteri panelinin alan adı için sunduğu her yönetim ekranını yanıtlar.

## Genel Bakış

Bir servis sağlayıcı modülü `RegistrarModule`'ü genişletir ve `coremio/modules/Registrars/{Ad}/{Ad}.php` altında yaşar. Gelen 21 modülden `ExampleRegistrarModule` bir sağlayıcı değil, yorumlu bir şablondur. Oradan başlayın: her isteğe bağlı metodu tam dönüş biçimiyle belgeler.

Alan adı hizmeti bir barındırma hizmeti değildir: sunucu kaydı ve kontrol paneli oturumu yoktur. Sağlayıcıya kendi API'siyle ulaşılır, çekirdek söylediğini aynalar. Bundan iki sonuç çıkar:

- **Her şey `$this->options` üzerinden okunur**, argüman olarak geçmez.
- **Neredeyse her metot isteğe bağlıdır.** Çekirdek `method_exists` ile yoklar; metodu olmayan ekran sunulmaz. Dört metotla yayınlayıp büyütün.

## Ön Koşullar

- Bir bayi ya da test hesabı ve API kimlik bilgileri. Tescil çoğu test ortamında bile para harcar; denemeden önce sağlayıcının test kipini doğrulayın.
- Modül iskeleti: bkz. [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi).
- Örnek `Modules::getInstance('Registrars', 'Acme')` ile alınır, `new` ile değil.
- `ExampleRegistrarModule`'ü kendi dizininize kopyalayın; sınıf adını, dosya adını ve yapılandırmadaki adı birlikte değiştirin.

## Yapı

```text
Acme.php        sınıf: extends RegistrarModule
ApiClient.php   HTTP istemcisi, initApi() içinde elle include edilir
config.php      ['meta' => [...], 'settings' => ['whois-types' => true, 'dns-record-types' => [...]]]
lang/en.php
lang/tr.php
logo.png
```

Şablon taşımayı sözleşmeden ayırır: `ApiClient.php` HTTP konuşur ve hizmetlerden habersizdir, modül sınıfı çekirdeğin sözcüklerini sağlayıcınınkine eşler.

> **API istemcisini kurucuda kurmayın**
> 
> `$this->config` ve `$this->service` yapıcı çalıştıktan *sonra* doldurulur. `__construct()` içinde kurulan istemci boş kimlik bilgisi okur ve hata yanlış API anahtarı gibi görünür. Tembel bir `initApi()` kullanın, sağlayıcıyla konuşan her metodun ilk satırında çağırın.

## Adım Adım

### API İstemcisini Tembel Bağlayın

```php
private function initApi(): void
{
    if ($this->api) return;

    include_once __DIR__ . DS . 'ApiClient.php';

    $this->api = new ApiClient($this->config['settings'] ?? []);

    // Her sağlayıcı çağrısı modül loguna düşer; bir tescil başarısız olduğunda
    // operatörün gerçekte ne gönderildiğini görebileceği tek yer orasıdır.
    $this->api->logger = fn ($action, $req, $resp) => $this->save_log($action, $req, $resp);
}
```

### Ayarları ve Bağlantı Testini Bildirin

`config_fields($data)` bildirin; ayar ekranı kendini kurar ve `$data` kaydedilmiş `config['settings']`'tir. `testConnection($config)` eklerseniz ekrana kimlik bilgilerini kanıtlayan bir buton gelir. Alan anahtarları `config.php` ile aynı olmalıdır.

### Yaşam Döngüsünü Yazın

Kullanılabilir bir modül için dört metot yeter: `check()`, `register()`, `renew()` ve `sync()`. `create()`'i asla yazmazsınız: o tabana aittir ve transfer koduna göre dallanır.

```php
// coremio/classes/RegistrarModule.php
$hasTcode = !empty($this->options['tcode']);
$method   = $hasTcode ? 'transfer' : 'register';

// Başvurusu yapılmış bir transfer ikinci kez gönderilmez: bekleyen bir olay,
// create()'in bunun yerine Services::check_transfer_status() ile bakmasını sağlar.
// Tescil kuruluşunun kuyruğa aldığı tescil de yeniden gönderilmez: create()
// sonucu Services::check_registration_status() ile sorar.

foreach (Hook::run('gate:domain.create', $this->service, $this->options, $method) as $veto)
    if (is_string($veto) && $veto !== '') throw new \Exception($veto);

$result = $this->{$method}();          // sizin metodunuz, argümansız

Hook::run('action:domain.created', $this->service, $result, $method);

// Başarılı bir transfer etkin bir alan adı anlamına GELMEZ: taban transfer
// olayını kaydeder ve hizmeti hâlâ işlemde olarak bildirir.
if ($hasTcode && $result !== false) return ['status' => 'inprocess'];

// Kuyruğa alınan tescil: register() ['status' => 'inprocess'] döndü. Taban
// bekleyen bir tescil kaydeder; sonucu registration_sync() sonra bildirir.
if (is_array($result) && ($result['status'] ?? '') === 'inprocess') return $result;
```

### Kuyruğa Alınan Tescili Bildirin

Bazı tescil kuruluşları siparişi kabul edip kararı sonra verir. O zaman `register()`'dan `['status' => 'inprocess']` döndürün; hizmet işlemde kalır ve sayfada "tescil devam ediyor" bandı görünür.

Sonra `registration_sync()` bildirin. `domain.registration` görevi, bandın "Şimdi Kontrol Et" butonu ve admin API onu `active` ya da `failed` dönene kadar çağırır. `active` hizmeti etkinleştirir, vade `endtime`'dan gelir; `failed` talebi kapatır, personele bildirir.

```php
public function registration_sync(): array|false
{
    $this->initApi();

    $domain = idn_to_ascii($this->options['domain'] ?? $this->service['name'], 0, INTL_IDNA_VARIANT_UTS46);

    // Alan adını değil SİPARİŞİ sorun: kuyruktaki ad, alan adı sorgusunda şimdiden görünebilir.
    $order = $this->api->call('domain/registration-status', ['domain' => $domain]);
    if ($order === false) return false;  // $this->error dolu: servis sağlayıcıya sorulamadı

    return match ($order['status'] ?? '') {
        'completed' => ['status' => 'active', 'endtime' => $order['expiration_date'] ?? ''],
        'failed'    => ['status' => 'failed', 'message' => $order['message'] ?? ''],
        default     => ['status' => 'pending', 'message' => $order['message'] ?? ''],
    };
}
```

Bu metot yoksa ad tescil edilince operatör hizmeti etkinleştirir; bu, talebi onaylar.

### TLD Kataloğunu Yayınlayın

`tlds()` bildirin ki operatör sağlayıcının listesini maliyetleri, yıl sınırları ve TLD başına seçenekleriyle içe aktarabilsin. `cost_prices()` yalnız fiyat taşıyan eski ikizidir ve `tlds()` yokken kullanılır.

### Yönetim Ekranlarını Ekleyin

Her biri tek bir metottur ve var olan her biri müşteri panelinde bir denetim açar.

## Referans

### Taban Sınıfın Verdikleri

```php
// Orkestrasyon: bu ikisini EZMEZSİNİZ
public function create(): array|bool;    // register() ya da transfer()'a dağıtır
public function renew(): array|bool;     // onun yerine renewal() bildirdiyseniz onu sarar

// Bağlam
public function set_service(array|int $service = []): void;
public function set_product(array|int $product = []): void;
public function set_order(array|int $order = []): void;
public static function get_doc_lang($param, $lang = '');

// İçe aktarım ve ayar ekranları
public function import_domain($data = []): array;
public function apply_import_tlds($data = []): bool;
public function controller_settings($extraFields = []): array;
public function controller_test_connection(): array;
public function controller_domains(): string;
public function controller_tlds(): string;
public function controller_import(): array;
public function controller_import_tld(): array;

// ModuleBaseTrait'ten
protected function save_log($action = '', $request = '', $response = '', $processed = ''): int|bool;
protected function encode_str(string $str = '', string $key = ''): string;
protected function decode_str(string $str = '', string $key = ''): string;
public function logo(): string;
```

- **create()**: Sağlama motorunun çağırdığı giriş noktası. Tescil mi transfer mi olduğunu seçer, veto kancasını çalıştırır, gönderilmiş transferi `['status' => 'inprocess']`'e çevirir ve kuyruğa alınan tescili kaydeder.
- **renew()**: İki şekil: ya `renew()`'u ezersiniz (bugün her modül böyle yapar) ya da `renewal()` bildirip tabana bırakırsınız. İkisi birden olmaz.
- **import_domain()**: `domains()`'in bir satırını yerel hizmete dönüştürür. Sizin tarafınız `domains()` ve `get_info()`'dur.
- **save_log()**: İsteği ve yanıtı modül loguna yazar. API istemcisini buradan geçirin.
- **$sample_data**: Sahte veriyle yanıt veren demo modülünde `true` yapın. Durum eşitlemesi bu modülleri atlar; uydurma bitiş tarihleri gerçek vadenin üzerine yazılmaz.

### Sizin Bildirdikleriniz

```php
public function check($sld = null, $tlds = []): array;   // müsaitlik, TLD'ye GÖRE anahtarlanır
public function register(): array|bool;
public function transfer(): array|bool;
public function renew(): array|bool;
public function sync(): array|false;                     // sağlayıcıdan durum + tarihler

// renew()'un eski alternatifi: renewal() bildirin, taban onun yerine onu çağırsın.
// Parametresiz bildirim çıplak çağrı demektir. Parametre varsa sıra register()
// sırası DEĞİLDİR: seçenek dizisi ÖNCE gelir ve $dns/$whois çifti hiç yoktur.
public function renewal($options, $domain, $sld, $tld, $year, $oduedate, $nduedate);
```

```php
private function initApi(): void;                        // sizin kendi yardımcınız, çekirdek kancası değil
public function config_fields($data = []): array;
public function testConnection($config = []): bool;

public function suspend(): bool;
public function unsuspend(): bool;
public function cancel(): bool;
public function restore(): bool;
public function is_inactive(): bool;
public function transfer_sync(): array|false;
public function registration_sync(): array|false;        // kuyruğa alınan tescilin sonucu
public function get_info(): array|false;

public function domains();                               // sağlayıcının alan adı listesi, içe aktarım için
public function tlds();                                  // fiyatlı katalog, cost_prices()'a üstün gelir
public function cost_prices($type = 'domain');

public function save_nameservers(array $dns): true;
public function get_contacts(): array|false;
public function save_contacts(array $whois): bool;
public function get_transfer_lock(): string;             // 'active' | 'passive'
public function toggle_transfer_lock(string $status): bool;
public function get_whois_privacy(): string;
public function toggle_whois_privacy(string $status): bool;
public function get_auth_code(): string|true;            // true = servis sağlayıcı e-postayla gönderdi

public function get_child_nameservers(): array|false;
public function add_child_nameserver(string $ns, string $ip): array;
public function save_child_nameserver(array $old, string $new_ns, string $new_ip): array;
public function delete_child_nameserver(string $ns, string $ip): bool;

public function get_dns_records(): array|false;
public function add_dns_record($type, $name, $value, $ttl, $priority);
public function update_dns_record($type = '', $name = '', $value = '', $identity = '', $ttl = '', $priority = '');
public function delete_dns_record($type = '', $name = '', $value = '', $identity = '');
public function get_dnssec_records(): array|false;
public function add_dnssec_record($digest, $key_tag, $digest_type, $algorithm);
public function delete_dnssec_record($digest, $key_tag, $digest_type, $algorithm, $identity = '');

public function get_forwarding();
public function set_forwarding($protocol = '', $method = '', $domain = '');
public function cancel_forwarding(): bool;
public function get_email_forwards();
public function add_email_forward($prefix = '', $target = '');
public function update_email_forward($prefix = '', $target = '', $target_new = '', $identity = '');
public function delete_email_forward($prefix = '', $target = '', $identity = '');

public function addon_create(array $addon): bool;
public function addon_suspend(array $addon): bool;
public function addon_unsuspend(array $addon): bool;
public function addon_cancel(array $addon): bool;
```

> **Eski argüman şekli karıştırılmaya çok müsait**
> 
> Taban metodunuzu Reflection ile inceler. Parametresiz bildirim çıplak çağrı demektir. Parametreliyse `register()` ve `transfer()` şu sırayla çağrılır: `($domain, $sld, $tld, $year, $dns, $whois, $wprivacy, $eppCode)`; `renewal()` *farklı* bir yedili sıra alır, seçenekler başta ve iletişim bilgisi yok. Bugünkü her modül parametresiz şekli kullanır ve `$this->options`'ı okur.

### $this->options Üzerinde Ne Var

- **domain**: Tam ad, Unicode. Göndermeden önce `idn_to_ascii()` ile çevirin.
- **name, sld, tld**: Parçalar. `name` ikinci seviye etikettir, `sld` eski takma adıdır; `$this->options['sld'] ?? $this->options['name'] ?? ''` okuyun.
- **year**: Dönem (yıl). Sırasıyla `$this->service['period_time']`'a, sonra 1'e düşün.
- **dns**: Ad sunucusu listesi. `array_values()`'ini geçin; sağlayıcılar dizi bekledikleri yerde JSON nesnesini reddeder.
- **whois**: İletişim kayıtları; anahtarlar `registrant`, `administrative`, `technical`, `billing`. Biçim aşağıda. Metin alanları Latin harflerle gelir. Temel sınıf, sizi çağırmadan önce ad, firma, adres, şehir ve eyalet alanlarını kişinin ülkesine göre Latin harflere çevirir. `save_contacts()` da aynı kopyayı alır; hizmette müşterinin yazdığı hâl kalır. `options/disable-domain-whois-transliterate` ayarı bu çeviriyi kapatır.
- **tcode**: Transfer yetkilendirme kodu. Varlığı, tabanı `register()` yerine `transfer()`'a yöneltir.
- **$this->docs**: Bir TLD'nin istediği ek belgeler; `settings['doc-fields'][$tld]` altında bildirilir. `file` alanı bir yol tutar, içeriği base64'leyin. Etiketler `get_doc_lang()`'den gelir.
- **$this->addon_params**: Çözülmüş ücretli eklentiler, örneğin `whois-privacy`. Sağlayıcının karşılığını alıp almayacağınıza buradan karar verin.

```php
$contact = $this->options['whois']['registrant'] ?? [];

// FirstName  LastName  Name  Company  EMail
// Country    City      State AddressLine1  AddressLine2  ZipCode
// PhoneCountryCode  Phone  FaxCountryCode  Fax
//
// Country iki harfli ISO kodudur. Name birleştirilmiş görüntüleme biçimidir ve
// okumada doldurulur; yazarken FirstName ile LastName'i verin.
```

### Ne Döndürürsünüz

| Metot | Biçim | Notlar |
| --- | --- | --- |
| `check()` | `[tld => ['status' => 'available'\|'unavailable']]` | TLD'ye göre anahtarlanır. Premium ad `premium => true` ve `premium_price => ['amount' => float, 'currency' => string]` ekler |
| `register()` | Başarıda `true` | Dizi dönüşü ek hizmet alanları taşıyabilir; `['status' => 'inprocess']` tescil kuruluşunun onu kuyruğa aldığı anlamına gelir; `false` hata kancasını tetikler |
| `transfer()` | Başvuruda `true` | Taban bunu `['status' => 'inprocess']`'e çevirir; tamamlanmaya `transfer_sync()` karar verir |
| `sync()` | `['creationtime', 'endtime', 'status']` | Tarihler `Y-m-d`; durum `active`, `expired`, `transferred` ya da `unknown` |
| `transfer_sync()` | aynı anahtarlar | Durum yalnız `active` ya da `pending` |
| `registration_sync()` | `['status', 'message', 'endtime']` | Durum `active`, `pending` ya da `failed`; `Y-m-d` biçimindeki `endtime` vade tarihi olur; `false` servis sağlayıcıya sorulamadığı anlamına gelir |
| `get_info()` | `['creation_time', 'end_time', 'ns1'..'ns4', 'whois', 'privacy', 'transferlock']` | Alt çizgili adlar `sync()`'ten farklıdır. İçe aktarım kullanır |
| `get_transfer_lock()` | `'active'` ya da `'passive'` | Kilitli olan `active`; yazan metot `'enable'`/`'disable'` alır |
| `get_contacts()` | `[tip => iletişim]` | Yazma tarafıyla aynı dört tip ve anahtarlar |

### TLD Kataloğunun Biçimi

`tlds()` ve `cost_prices()` üç biçimi kabul eder; içe aktarımda hepsi genişletilmiş olana normalleştirilir. Genişletilmiş biçim, yıl sınırlarını ve sipariş formunun sunabileceği seçenekleri taşır.

```php
// Biçim 1, yalnız fiyat. Modülün kendi maliyet para biriminde fiyatlanır; bu da
// settings['cost-currency'] altındaki tam sayı para birimi id'sidir (varsayılan 4, USD).
return [
    'com' => ['register' => 9.90, 'transfer' => 9.90, 'renewal' => 9.90],
];

// Biçim 1.5, tip başına tek tutar, kendi para birimiyle
return [
    'com' => ['price' => ['register' => ['amount' => 9.90, 'currency' => 'USD']]],
];

// Biçim 2, önerilen: yıl başına, para birimi başına, yetenek bayraklarıyla
return [
    'com' => [
        'min_years'     => 1,
        'max_years'     => 10,        // müşteri panelinin uyguladığı yenileme dönemi tavanı
        'whois_privacy' => true,
        'epp_code'      => true,
        'dns_manage'    => true,
        'paperwork'     => false,

        // İçteki anahtar bir para birimidir: ISO kodu ya da tam sayı id, ikisi de çözülür.
        // TLD başına para birimi anahtarı yoktur; yedek settings['cost-currency']'dir.
        'pricing' => [
            'register' => [
                1 => ['USD' => ['cost' => 9.90, 'promo' => 7.90]],
                2 => ['USD' => ['cost' => 19.00]],
            ],
            'renewal'  => [1 => ['USD' => ['cost' => 9.90]]],
            'transfer' => [1 => ['USD' => ['cost' => 9.90]]],
        ],
    ],
];
```

## Örnek

Bir tescil, ardından yerel kaydı dürüst tutan eşitleme. `register()` gönderip unutur; `sync()` alan adının sağlayıcının tarihleriyle var olduğunu sonradan kanıtlar.

```php
public function register(): array|bool
{
    $this->initApi();

    // Girdi Unicode, çıktı punycode. Her sağlayıcı ASCII ister.
    $domain = idn_to_ascii($this->options['domain'] ?? $this->service['name'], 0, INTL_IDNA_VARIANT_UTS46);
    $tld    = (string) ($this->options['tld'] ?? '');
    $year   = (int) ($this->options['year'] ?? $this->service['period_time']) ?: 1;
    $whois  = $this->options['whois'] ?? [];
    $dns    = $this->options['dns'] ?? [];

    $params = [
        'domain' => $domain,
        'year'   => $year,
        'dns'    => array_values($dns),
    ];

    // Ücretli whois-privacy eklentisi, bu sipariş bir tane taşıyorsa.
    if ((bool) ($this->addon_params['whois-privacy'] ?? false))
        $params['privacy_protection'] = true;

    // config settings['doc-fields'][$tld] altında bildirilen TLD evrakı.
    foreach ($this->config['settings']['doc-fields'][$tld] ?? [] as $docId => $doc) {
        if (($doc['required'] ?? false) && strlen((string) ($this->docs[$docId] ?? '')) < 1)
            throw new Exception('The document "' . self::get_doc_lang($doc['name']) . '" is not specified!');

        $value = $this->docs[$docId] ?? '';

        // Dosya alanı baytları değil bir YOL tutar.
        if (($doc['type'] ?? '') === 'file') $value = base64_encode((string) file_get_contents($value));

        $params['documents'][$docId] = $value;
    }

    // Bizim dört iletişim tipimiz, sağlayıcının adlarına eşlenmiş hâlde.
    $map = ['registrant' => 'owner', 'administrative' => 'admin', 'technical' => 'tech', 'billing' => 'billing'];

    foreach ($map as $ours => $theirs)
        $params['contacts'][$theirs] = [
            'first_name' => $whois[$ours]['FirstName'] ?? '',
            'last_name'  => $whois[$ours]['LastName'] ?? '',
            'company'    => $whois[$ours]['Company'] ?? '',
            'email'      => $whois[$ours]['EMail'] ?? '',
            'address1'   => $whois[$ours]['AddressLine1'] ?? '',
            'city'       => $whois[$ours]['City'] ?? '',
            'state'      => $whois[$ours]['State'] ?? '',
            'zip'        => $whois[$ours]['ZipCode'] ?? '',
            'country'    => $whois[$ours]['Country'] ?? '',
            'phone_cc'   => $whois[$ours]['PhoneCountryCode'] ?? '',
            'phone'      => $whois[$ours]['Phone'] ?? '',
        ];

    $this->api->call('domain/register', $params);

    return true;
}
```

```php
public function sync(): array|false
{
    $this->initApi();

    $domain  = idn_to_ascii($this->options['domain'] ?? $this->service['name'], 0, INTL_IDNA_VARIANT_UTS46);
    $details = $this->api->call('domain/info', ['domain' => $domain]);

    if (!$details) return false;        // false "soramadım" demektir, "yok oldu" DEĞİL

    $map = [
        'active'                => 'active',
        'expired'               => 'expired',
        'transferred-elsewhere' => 'transferred',
    ];

    return [
        'creationtime' => DateManager::format('Y-m-d', $details['creation_date'] ?? ''),
        'endtime'      => DateManager::format('Y-m-d', $details['expiration_date'] ?? ''),

        // Eşleyemediğiniz her şey 'unknown' olur. Asla 'active' tahmin etmeyin:
        // eşitleme vade tarihini yazar ve yanlış bir tahmin hiçbir şeyi yenilemez.
        'status'       => $map[strtolower((string) ($details['status'] ?? ''))] ?? 'unknown',
    ];
}
```

Zamanlanmış durum eşitlemesi bunu çağırır, yanıtı yerel hizmetle karşılaştırır ve vadeyi taşır. `false` ile `'unknown'` çekirdeğe yerel kayda dokunmamasını söyler.

## Tuzaklar

> **XML konuşan sağlayıcı kendi ayrıştırıcısını taşır**
> 
> `Utility::xdecode()` `@attributes` girdisi üretmez ve tekrar eden elemanları bozar. Anlamı özniteliklerde ya da tekrarlı listede duran yanıt sessizce yanlış çözülür; XML'i modülün içinde ayrıştırın.

> **Başvurusu yapılmış transfer, sahip olunmuş alan adı değildir**
> 
> `transfer()`'ın true dönmesi yalnız isteğin kabul edildiği anlamına gelir; alan adı ancak `transfer_sync()` öyle dediğinde etkinleşir.

> **Kuyruktaki tescili sync() ile yanıtlamayın**
> 
> Birçok tescil kuruluşu kuyruktaki adı `pendingCreate` tutar; bir durum eşlemesi onu etkin okur. `registration_sync()`'i sipariş kaydından yanıtlayın, yoksa hizmet ad oluşmadan etkinleşir.

> **Her sınırda punycode'a çevirin**
> 
> Ad sunucuları da uluslararası olabilir. Şablon, kaydetmeden önce her girdiye `idn_to_ascii()` uygular.

> **Modülün yanındaki yardımcı sınıf otomatik yüklenmez**
> 
> Otomatik yükleyici modül tipi dizinlerini eşler, içindeki dosyaları değil; istemciyi `initApi()` içinden `include_once` edin. Bir kanca içindeki eksik sınıftan doğan ölümcül hata yutulur ve o kancanın geri kalanını da düşürür.

> **Her çağrıyı loglayın, pahalı olanı en sona bırakın**
> 
> İstemcinin loglayıcısını ilk gün `save_log()`'a bağlayın, sonra maliyet sırasıyla ilerleyin: bağlantı testi, müsaitlik sorgusu, bilgi veren ekranlar ve ancak sonra gerçek bir tescil.

## İlgili Makaleler

- [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi)
- [Modül Yaşam Döngüsü](https://dev.wisecp.com/tr/modul-yasam-dongusu)
- [Modül Yapılandırması](https://dev.wisecp.com/tr/modul-yapilandirmasi)
- [Ödeme Geçidi Yazma](https://dev.wisecp.com/tr/odeme-geciti-yazma)
- [Sunucu Modülü Yazma](https://dev.wisecp.com/tr/sunucu-modulu-yazma)
- [Hata Yönetimi](https://dev.wisecp.com/tr/hata-yonetimi)
- [Alan Adı Temelleri](https://dev.wisecp.com/tr/alan-adi-temelleri)
