Servis Sağlayıcı Modülü 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_existsile 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,newile 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ı
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.
$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
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.
// 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.
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
// 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;
['status' => 'inprocess']'e çevirir ve kuyruğa alınan tescili kaydeder.
renew()'u ezersiniz (bugün her modül böyle yapar) ya da renewal() bildirip tabana bırakırsınız. İkisi birden olmaz.
domains()'in bir satırını yerel hizmete dönüştürür. Sizin tarafınız domains() ve get_info()'dur.
true yapın. Durum eşitlemesi bu modülleri atlar; uydurma bitiş tarihleri gerçek vadenin üzerine yazılmaz.
Sizin Bildirdikleriniz
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);
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;
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
idn_to_ascii() ile çevirin.
name ikinci seviye etikettir, sld eski takma adıdır; $this->options['sld'] ?? $this->options['name'] ?? '' okuyun.
$this->service['period_time']'a, sonra 1'e düşün.
array_values()'ini geçin; sağlayıcılar dizi bekledikleri yerde JSON nesnesini reddeder.
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.
register() yerine transfer()'a yöneltir.
settings['doc-fields'][$tld] altında bildirilir. file alanı bir yol tutar, içeriği base64'leyin. Etiketler get_doc_lang()'den gelir.
whois-privacy. Sağlayıcının karşılığını alıp almayacağınıza buradan karar verin.
$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.
// 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.
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;
}
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
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.
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.
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.
Ad sunucuları da uluslararası olabilir. Şablon, kaydetmeden önce her girdiye idn_to_ascii() uygular.
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.
İ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
Geri bildiriminiz için teşekkürler!
Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.