Dolandırıcılık Modülü Yazma

1.7k görüntülenme Markdown

Dolandırıcılık modülü, hiçbir şey kaydedilmeden önce sipariş denemesini inceler ve tek bir soruyu yanıtlar: ilerleyebilir mi.

Genel Bakış

Bir dolandırıcılık modülü FraudModule'ü genişletir ve coremio/modules/Fraud/{Ad}/{Ad}.php altında yaşar. Her şekilden bir tane gelir: WFraud yerel veriden puanlar, MaxMind denemeyi dış bir sağlayıcıya iletir.

Kapı bir kez, ödeme akışından, sipariş, fatura ya da hizmet yokken çalışır.

coremio/operations/ClientCheckout.php
// Bu noktada hiçbir şey kalıcılaştırılmamıştır: engellenen bir deneme geriye
// ne sipariş, ne fatura, ne de hizmet bırakır.
if ($fraudError = \FraudModule::run_checks($this->fraud_payload($pricing, $pmethod, (float) $taxCalc['total'], $member)))
    throw new \Exception($fraudError);

Bu kapının iki özelliği modülü nasıl yazacağınızı belirler:

  • Fırlatan modül geçmiş sayılır, hata uyarı olarak loglanır.
  • Engelleme, false dönüşü artı bir mesaj. Puan, inceleme durumu ya da kuyruk yoktur.

Ön Koşullar

  • Yükün ne taşıdığını bilin; orada olmayanı kendiniz getirirsiniz.
  • Uzaktan puanlıyorsanız bir sağlayıcı hesabı; yerel puanlıyorsanız ek bir şey gerekmez.
  • Modül iskeleti: bkz. Modül Anatomisi.
  • Tekrar tekrar verebileceğiniz bir test siparişi. Fazla engelleyen kural yalnız kayıtlar tablosunda görünür.

Yapı

coremio/modules/Fraud/Acme/
Acme.php        namespace WISECP\Modules\Fraud; class Acme extends FraudModule
config.php      ['meta' => [...], 'status' => false, 'settings' => []]
lang/en.php
lang/tr.php
logo.png
status üst seviye bir anahtar, meta'nın parçası değil

run_checks() $row['config']['status'] true değilse modülü atlar; ayar ekranı da oraya yazar. meta altında sorunsuz kaydedilir ve hiç çağrılmaz.

Ayar ekranını siz kurmazsınız; onu taban birleştirir. Durum anahtarı kapalıyken bütün alanlar devre dışıdır.

Adım Adım

Sınıfı Bildirin

Acme.php
<?php
namespace WISECP\Modules\Fraud;

// Dosya bir ad alanı içindedir, yani her global sınıf import edilmeli ya da
// başına ters bölü konmalıdır. Nitelenmemiş bir ad bu ad alanının içine çözülür
// ve çalışma anında patlar; sözdizimi denetimi bunu yakalamaz.
use FraudModule;
use Language;
use User;
use UserManager;

class Acme extends FraudModule
{
    public function check($params = []): bool
    {
        return true;
    }
}

Ayar Alanlarını Bildirin

fields() alan tanımları döner. Anahtarlar: type, name, description, value, anahtar için checked, açılır liste için options. Güncel değerler $this->config['settings']'ten gelir.

Bozuk Kimlik Bilgisini Kaydetmeden Reddedin

save_fields() bildirirseniz kaydetme onu kalıcılaştırmadan önce çalıştırır. Açılmanın gerçek bir iş yapması gerekiyorsa activate() ve deactivate() bildirin.

Kuralı Yazın

Yükü okuyun ve karar verin. Engellerken sırayla: $this->error'ı çevrilmiş bir cümleye ayarlayın, insert_record() çağırın, false dönün.

İki Yönü de Doğrulayın

  1. Geçmesi gereken bir sipariş verin. Tamamlanır ve kayıtlar tablosu boş kalır.
  2. Engellenmesi gereken birini verin. Ödeme ekranı mesajınızı gösterir, hiçbir şey oluşmaz.
  3. Sağlayıcıyı yanlış bir anahtarla bozun. Sipariş yine tamamlanır ve log modülünüzün adını taşıyan bir uyarı içerir.

Referans

Taban Sınıfın Verdikleri

FraudModule imzaları
// Kapı. Ödeme akışı çağırır, bir modül asla çağırmaz.
public static function run_checks(array $params = []): string;   // '' = devam, aksi hâlde mesaj

// Kayıtlar
public function insert_record($user_id = 0, $message = '', $ip = '');
public function records($rCount = false, $filters = [], $orders = [], $start = 0, $end = -1);
public function record_table(): WISECP\Components\Table|string;

// Ayar ekranı, sizin için birleştirilir
public function page_settings(): string;
public function save_config($data = []): int|bool;

// Yönetici controller'ları, operation=module_controller&controller={name} ile erişilir
public function controller_records(): string;
public function controller_save_settings(): array;
public function controller_clear_records(): array;

// Public durum
public ?string $error;     // false döndüğünüzde müşterinin gördüğü mesaj
public ?array  $config;    // ['status' => bool, 'settings' => [...], 'meta' => [...]]
public ?array  $lang;
public ?array  $user;      // giriş yapmış üye, yapıcıda çözülür
public ?array  $admin;
public ?string $dir;
public string  $url;       // dikkat: komşularının aksine nullable değil
public ?string $area_link;
run_checks() config['status']'ü true olan her modülü kayıt sırasıyla gezer, check metodu olmayanı atlar, ilk false'ta döner. Boş dize "ilerleyebilir" demektir.
insert_record() fraud_detected_records'a modül adınız, kullanıcı id'si, gerekçeniz ve IP ile tek satır yazar. IP boşsa istek IP'si kullanılır.
records() Modülünüze göre süzülmüş, müşteriye bağlanmış hâlde geri okur.
page_settings() Ayar ekranını kurar: durum anahtarı, alanlarınız, kayıtlar sekmesi ve setConfigureTab()'in eklediği sekmeler. Ezmeyin, geri çağrımı kullanın.
$this->error Boş hatayla engellemek de engeller: kapı, modül adınızla website/checkout/error-fraud'a düşer.

Sizin Bildirdikleriniz

biri zorunlu, beşi isteğe bağlı
// ZORUNLU. true = geçir, false = engelle ($this->error ayarlanmış olarak).
public function check($params = []): bool;

// İSTEĞE BAĞLI
public function fields(): array;                              // ayar formu tanımlayıcıları
public function save_fields($fields = []): array;             // kalıcılaştırmadan önce doğrula
public function activate(): bool;                             // modülü açma
public function deactivate(): bool;                           // modülü kapatma
public function setConfigureTab(\WISECP\Components\Tab $obj): void;   // ek ayar sekmeleri
save_fields ya alanları ya bir hata zarfı döner

Başarıda (gerekirse temizlenmiş) alan dizisini dönün; o config['settings'] olur. Başarısızlıkta ['status' => 'error', 'message' => '...'] dönün, kaydetme fırlatır.

$params İçinde Ne Var

Ödeme akışı bunu kapıdan hemen önce kurar; girdinin tamamı budur.

user_data id, email, name, surname, full_name, phone, country, blacklist, company_name, identity, gsm_cc, gsm_number, ip, user_agent ve ayrıca address. Cep telefonu gsm_number'dır, gsm değil.
user_data.address address, city, country_code, zipcode. Form yalnız ülke id'si gönderdiyse ülke kodu ondan çözülür.
misafir denemesi user_data.id 0'dır ve kimlik gönderilen formdan gelir; yalnız email, name, surname ve full_name dolar. Profil alanları boş değil, hiç yoktur.
currency Para birimi id'si. Sağlayıcı ISO kodu istiyorsa Money::Currency() ile çözün.
total Float; o para biriminde, vergi ve ücretler dahil.
pmethod Ödeme modülünün adı, örneğin PayTR. MaxMind bunu payment-gateways tablosuyla kendi sözcüklerine eşler.
items Fiyatlanmış hâliyle sepet satırları.
discounts ['items' => ['coupon' => [['name' => 'KOD'], ...]]]. Üç seviye iç içedir, kupon kullanılmadıysa hiç yoktur.

İki Şekil

YönYerel puanlamaUzak puanlama
ReferansWFraudMaxMind
KurallarKara liste, IP ülkesine karşı fatura ülkesi, proxy ya da VPNTek sağlayıcı puanının eşikle karşılaştırılması
AyarlarKural başına bir anahtarKimlik bilgileri, sağlayıcı katmanı, risk puanı, sağlayıcı anahtarları
Kimlik bilgisiYokZorunlu; save_fields()'te doğrulanır
Sipariş başına maliyetSıfırFaturalanabilir bir API çağrısı
action:module.fraud_settings_saved Bir dolandırıcılık modülünün ayarları yazıldıktan sonra modül adı ve yapılandırmayla çalışır.

Örnek

Anahtarı, eşiği ve kaydı olan bir kural. Sözleşme alan anahtarı: fields() bildirir, save_fields() doğrular, check() okur.

Acme.php, ayarlar
public function fields(): array
{
    $settings = $this->config['settings'] ?? [];

    return [
        'api-key' => [
            'wrap_width' => 100,
            'type'       => 'text',
            'name'       => $this->lang['api-key'] ?? 'API Key',
            'value'      => $settings['api-key'] ?? '',
        ],
        'risk-score' => [
            'wrap_width'  => 100,
            'width'       => 15,
            'type'        => 'number',
            'name'        => $this->lang['risk-score'] ?? 'Risk Score',
            'description' => $this->lang['risk-score-desc'] ?? '',
            'value'       => $settings['risk-score'] ?? '20',
        ],
        'block-country-mismatch' => [
            'wrap_width' => 100,
            'type'       => 'approval',      // bir anahtar: 'value', kutu işaretliyken gönderilen değerdir
            'name'       => $this->lang['country-mismatch'] ?? 'Block country mismatch',
            'value'      => 1,
            'checked'    => $settings['block-country-mismatch'] ?? false,
        ],
    ];
}

public function save_fields($fields = []): array
{
    // Burada reddetmek, modülün yarım yapılandırılmışken açılmayı geri çevirme yoludur.
    if (\Validation::isEmpty($fields['api-key'] ?? ''))
        return ['status' => "error", 'message' => $this->lang['error-is-empty'] ?? 'API key is required.'];

    return $fields;
}
Acme.php, karar
public function check($params = []): bool
{
    $settings = $this->config['settings'] ?? [];
    $user     = (array) ($params['user_data'] ?? $this->user ?? []);
    $ip       = (string) ($user['ip'] ?? '');
    $uid      = (int) ($user['id'] ?? 0);          // misafir denemesinde 0

    // Her kural bağımsız olarak açılıp kapanır, yani değeri değil bayrağı okuyun.
    if ((int) ($settings['block-country-mismatch'] ?? 0) === 1) {
        $billing = strtoupper((string) ($user['address']['country_code'] ?? ''));
        $origin  = strtoupper((string) (UserManager::ip_info($ip)['countryCode'] ?? ''));

        // Yalnız KESİN bir uyuşmazlık engeller. Aksi hâlde çözülemeyen bir IP ya da
        // eksik bir fatura ülkesi, her yarım profili bir yanlış pozitife çevirirdi;
        // burada bir yanlış pozitif ise kaybedilmiş bir satıştır.
        if ($billing !== '' && $origin !== '' && $billing !== $origin) {
            // Bunun için çekirdekte bir dize var; WFraud da aynı anahtarı kullanır.
            $this->error = Language::gc("website/checkout/error-fraud-country");
            $this->insert_record($uid, "IP country ({$origin}) does not match billing country ({$billing})", $ip);

            return false;
        }
    }

    // Uzak bir puan. Buradaki hiçbir hata engellememeli: kapı fırlatılan bir
    // istisnayı geçiş sayar, bu erken dönüş de bozuk bir yanıt için aynısını yapar.
    $score = $this->remote_score($params);
    if ($score === null) return true;

    if ($score >= (int) ($settings['risk-score'] ?? 20)) {
        // Kendi ifadeniz SİZİN dil dosyanıza aittir. Language::gc(), çekirdekte
        // olmayan bir anahtar için false döner; bu da $error'ı boş bırakır ve
        // genel "güvenlik denetimlerimizden geçemedi" cümlesine düşülür.
        $this->error = $this->lang['error-risk-score'] ?? 'Your order could not be approved automatically.';
        $this->insert_record($uid, "Risk score {$score} reached the configured threshold", $ip);

        return false;
    }

    return true;
}
kapı bir false ile ne yapar
// coremio/classes/FraudModule.php, run_checks()
foreach ($modules as $name => $row) {
    if (!(bool) ($row['config']['status'] ?? false)) continue;      // etkin değil: atlanır

    try {
        $module = Modules::getInstance("Fraud", (string) $name);
        if (!$module || !method_exists($module, 'check')) continue;
        if ($module->check($params) !== false) continue;            // geçti: sonraki modül

        $message = trim((string) ($module->error ?? ''));

        return $message !== ''
            ? $message
            : Language::gc("website/checkout/error-fraud", ['{module}' => (string) $name]);
    }
    catch (\Throwable $e) {
        // Bir sağlayıcının çökmesi ödeme akışını da beraberinde çökertmemelidir.
        Logger::warning("Fraud module '{$name}' check failed: " . $e->getMessage());
    }
}

return '';   // etkin her modül geçti

Dönen dize, ödeme akışının fırlattığı istisnaya dönüşür; müşteri sizin $this->error'unuzu okur. Ayrıntıyı kayda bırakın.

Tuzaklar

Yanlış pozitif, reddedilmiş bir müşteridir

Olumlu kanıt arayın: doğrulanmış uyuşmazlıkta engelleyin, eksik değerde asla. Buradaki hata, hiç verilmemiş siparişler olarak görünür.

Sağlayıcı hatasında asla kapalı düşmeyin

Zaman aşımı dolandırıcılık kanıtı değildir. İstisna kaçsın ya da true dönün; kapı bir uyarı loglar ve sipariş geçer.

Global sınıflarınızı import edin

Dolandırıcılık modülleri ad alanı içindedir; çıplak bir UserManager::ip_info() WISECP\Modules\Fraud\UserManager'a çözülür ve patlar. Global sınıfları import edin ya da başına ters bölü koyun.

Misafir denemesini karşılayın

Misafir denemesinde user_data.id'ye dayanan kural ya çöker ya sessizce hiç çalışmaz.

Her engellemeyi gerekçesiyle kaydedin

Operatörün kapıya açılan tek penceresi kayıtlar sekmesidir. Kaydı olmayan bir engelleme, izsiz kaybolmuş bir sipariştir.

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.