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

1.7k görüntülenme Markdown

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.
  • Ö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ı

coremio/modules/Registrars/Acme/
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

initApi()
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.

tabandaki create()
// 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.

Acme.php, registration_sync()
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

RegistrarModule, tam imzalar
// 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

çekirdek dörtlü, artı eski renewal
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);
isteğe bağlı: durum, katalog, yönetim
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.
tek bir iletişim kaydı, tam olarak bu anahtarlar
$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

MetotBiçimNotlar
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 trueDizi 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 trueTaban 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ı anahtarlarDurum 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.

kabul edilen üç biçim
// 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.

Acme.php, register()
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;
}
Acme.php, sync() ve çekirdeğin sakladığı biçim
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.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.