WordPress Eklentisinde Freemium Model ile Premium Sürüm Nasıl Oluşturulur

Bir WordPress eklentisi geliştirdiniz, aylarca emek verdiniz ve sonunda yayınladınız. Peki ya para kazanma kısmı? Freemium model, bu noktada en sağlıklı ve sürdürülebilir yollardan biri. Ama doğru kurgulanmadığında kullanıcıyı kaybetmek de işten bile değil. Bu yazıda WordPress eklentinize premium katmanı nasıl eklersiniz, hangi teknik yapıları kullanırsınız ve gerçek dünyada nasıl çalışır, bunları konuşacağız.

Freemium Modelin Mantığı: Neyi Ücretsiz, Neyi Ücretli Bırakmalısınız?

Burası teknik olmaktan çok stratejik bir karar noktası. Ücretsiz sürüm, insanları içeri çekecek kadar işe yaramalı ama tek başına yeterli olmamalı. Premium sürüm ise “keşke bunu baştan alsaydım” dedirtecek kadar değerli olmalı.

Kötü freemium örnekleri genellikle şöyle hata yapar: Ya ücretsiz sürüm o kadar kısıtlıdır ki kimse indirip denemez, ya da o kadar tamdır ki premium’a geçmek için hiçbir neden yoktur.

Pratik öneri olarak şunu söyleyebilirim: Temel işlevi ücretsiz verin, gelişmiş entegrasyonları, otomasyonları ve raporlama özelliklerini ücretli yapın. Örneğin bir form eklentisinde sınırsız form alanı ücretsiz olabilir ama koşullu mantık, Zapier entegrasyonu ve PDF export premium’da olmalı.

Lisans Sistemi Kurgusu: EDD ve Freemius Karşılaştırması

Premium katman için iki ana yol var: Kendi lisans sunucunuzu kurmak ya da hazır bir platform kullanmak.

Easy Digital Downloads (EDD) + Software Licensing yaklaşımında kendi WordPress siteniz lisans sunucusu oluyor. Tam kontrol sizdeyken hem teknik hem de idari yük de size ait.

Freemius ise daha entegre bir çözüm sunuyor: Ödeme, lisans yönetimi, kullanım analitikleri, A/B testleri hepsi bir arada. Yeni başlıyorsanız Freemius ile başlamak mantıklı.

Biz burada her iki senaryoyu da ele alacağız ama asıl mimariyi kendi lisans sunucusu üzerinden kuracağız, çünkü bu sayede her detayı anlayabilirsiniz.

Eklenti Dosya Yapısı

Önce sağlam bir dosya yapısı oluşturalım:

my-awesome-plugin/
├── my-awesome-plugin.php          # Ana eklenti dosyası
├── includes/
│   ├── class-core.php             # Ücretsiz özellikler
│   ├── class-license.php          # Lisans kontrolü
│   └── class-premium.php          # Premium özellikler
├── admin/
│   ├── class-admin.php
│   └── views/
│       ├── settings.php
│       └── license.php
├── assets/
│   ├── js/
│   └── css/
└── languages/

Bu yapıda premium özellikler ayrı bir sınıfta tutuluyor. Neden? Çünkü ilerleyen dönemde “premium” klasörünü ayrı bir pakette sunmak ya da şifrelemek isteyebilirsiniz.

Ana Eklenti Dosyasında Temel Kurulum

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Plugin URI:  https://example.com/my-awesome-plugin
 * Description: Harika bir eklenti - ücretsiz ve premium özelliklerle.
 * Version:     1.0.0
 * Author:      Sizin Adınız
 * License:     GPL-2.0+
 * Text Domain: my-awesome-plugin
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'MAP_VERSION', '1.0.0' );
define( 'MAP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MAP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'MAP_LICENSE_SERVER', 'https://yourstore.com' );

// Lisans durumunu bir kez kontrol et, önbelleğe al
function map_is_premium() {
    $license_status = get_option( 'map_license_status' );
    return ( $license_status === 'valid' );
}

// Sınıfları yükle
require_once MAP_PLUGIN_DIR . 'includes/class-core.php';
require_once MAP_PLUGIN_DIR . 'includes/class-license.php';

if ( map_is_premium() ) {
    require_once MAP_PLUGIN_DIR . 'includes/class-premium.php';
}

// Başlat
add_action( 'plugins_loaded', function() {
    new MAP_Core();
    new MAP_License();

    if ( map_is_premium() ) {
        new MAP_Premium();
    }
});

Dikkat edin: class-premium.php dosyası sadece lisans geçerliyse yükleniyor. Bu hem performans hem de güvenlik açısından önemli.

Lisans Doğrulama Sınıfı

Şimdi en kritik parça olan lisans yönetimini yazalım:

<?php
class MAP_License {

    private $license_key;
    private $license_status;

    public function __construct() {
        $this->license_key    = get_option( 'map_license_key', '' );
        $this->license_status = get_option( 'map_license_status', 'invalid' );

        add_action( 'admin_menu', [ $this, 'add_license_page' ] );
        add_action( 'admin_init', [ $this, 'handle_license_actions' ] );

        // Haftalık otomatik doğrulama
        if ( ! wp_next_scheduled( 'map_weekly_license_check' ) ) {
            wp_schedule_event( time(), 'weekly', 'map_weekly_license_check' );
        }
        add_action( 'map_weekly_license_check', [ $this, 'verify_license_remote' ] );
    }

    public function activate_license( $license_key ) {
        $response = wp_remote_post( MAP_LICENSE_SERVER . '/wp-json/lmfwc/v2/licenses/activate/' . $license_key, [
            'timeout' => 15,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode( LICENSE_API_USER . ':' . LICENSE_API_PASS ),
            ],
            'body'    => [
                'site_url' => get_site_url(),
            ],
        ]);

        if ( is_wp_error( $response ) ) {
            return [ 'success' => false, 'message' => $response->get_error_message() ];
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( isset( $body['data']['status'] ) && $body['data']['status'] === 3 ) {
            update_option( 'map_license_key', sanitize_text_field( $license_key ) );
            update_option( 'map_license_status', 'valid' );
            update_option( 'map_license_expires', $body['data']['expiresAt'] ?? '' );
            return [ 'success' => true, 'message' => 'Lisans başarıyla aktive edildi.' ];
        }

        return [ 'success' => false, 'message' => 'Geçersiz lisans anahtarı.' ];
    }

    public function verify_license_remote() {
        if ( empty( $this->license_key ) ) {
            return;
        }

        $response = wp_remote_get( MAP_LICENSE_SERVER . '/wp-json/lmfwc/v2/licenses/' . $this->license_key, [
            'timeout' => 15,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode( LICENSE_API_USER . ':' . LICENSE_API_PASS ),
            ],
        ]);

        if ( is_wp_error( $response ) ) {
            // Sunucu ulaşılamazsa mevcut durumu koru, kullanıcıyı hemen kısıtlama
            return;
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        // Status 3 = aktif, diğerleri geçersiz
        $is_valid = isset( $body['data']['status'] ) && $body['data']['status'] === 3;
        update_option( 'map_license_status', $is_valid ? 'valid' : 'invalid' );
    }

    public function handle_license_actions() {
        if ( ! isset( $_POST['map_license_action'] ) ) {
            return;
        }

        if ( ! check_admin_referer( 'map_license_nonce' ) ) {
            wp_die( 'Güvenlik hatası.' );
        }

        if ( $_POST['map_license_action'] === 'activate' ) {
            $key    = sanitize_text_field( $_POST['map_license_key'] ?? '' );
            $result = $this->activate_license( $key );
            $type   = $result['success'] ? 'updated' : 'error';
            add_settings_error( 'map_license', 'map_license', $result['message'], $type );
        }
    }

    public function add_license_page() {
        add_options_page(
            'Lisans Yönetimi',
            'MAP Lisans',
            'manage_options',
            'map-license',
            [ $this, 'render_license_page' ]
        );
    }

    public function render_license_page() {
        settings_errors( 'map_license' );
        include MAP_PLUGIN_DIR . 'admin/views/license.php';
    }
}

Burada önemli bir nokta: Lisans sunucusuna ulaşılamazsa mevcut durumu koruyoruz. Kullanıcının sunucusu geçici olarak lisans doğrulayamazsa premium özellikler kaybolmamalı. Bu hem kullanıcı deneyimi hem de güven açısından kritik.

Premium Özellikleri Koşullu Gösterme

Ücretsiz kullanıcıya premium özelliği tamamen gizlememek, onun yerine “bu özellik premium’da var” diye göstermek daha iyi bir strateji. Buna feature gating deniyor:

<?php
class MAP_Core {

    public function __construct() {
        add_action( 'admin_menu', [ $this, 'add_admin_menu' ] );
        add_shortcode( 'map_display', [ $this, 'render_shortcode' ] );
    }

    public function render_settings_tab_advanced() {
        if ( ! map_is_premium() ) {
            ?>
            <div class="map-premium-notice">
                <span class="dashicons dashicons-lock"></span>
                <strong>Bu özellik Premium sürümde mevcuttur.</strong>
                <a href="https://yourstore.com/pricing" target="_blank" class="button button-primary">
                    Premium'a Geç
                </a>
                <p>Gelişmiş filtreler, koşullu mantık ve otomatik raporlama için premium'a yükseltin.</p>
            </div>
            <?php
            return;
        }

        // Premium içerik burada
        $this->render_advanced_settings();
    }

    public function get_items( $limit = 10 ) {
        // Ücretsiz sürümde maksimum 10 kayıt
        if ( ! map_is_premium() && $limit > 10 ) {
            $limit = 10;
        }

        return get_posts([
            'post_type'      => 'map_item',
            'posts_per_page' => $limit,
            'post_status'    => 'publish',
        ]);
    }
}

Bu yaklaşımın güzelliği şu: Kullanıcı “neden göremiyorum” diye destek talebi açmıyor. Kilidi görüyor, ne yapması gerektiğini anlıyor.

Otomatik Güncelleme Mekanizması

WordPress’in kendi güncelleme sistemi sadece wordpress.org’daki eklentiler için çalışıyor. Premium eklentiler için kendi güncelleme altyapınızı kurmanız gerekiyor:

<?php
class MAP_Updater {

    private $plugin_slug;
    private $plugin_file;
    private $update_url;
    private $license_key;

    public function __construct( $plugin_file ) {
        $this->plugin_file  = $plugin_file;
        $this->plugin_slug  = plugin_basename( $plugin_file );
        $this->update_url   = MAP_LICENSE_SERVER . '/wp-json/map/v1/check-update';
        $this->license_key  = get_option( 'map_license_key', '' );

        add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] );
        add_filter( 'plugins_api', [ $this, 'plugin_info' ], 10, 3 );
        add_filter( 'upgrader_package_options', [ $this, 'add_auth_to_package' ] );
    }

    public function check_for_update( $transient ) {
        if ( empty( $transient->checked ) ) {
            return $transient;
        }

        $remote = $this->get_remote_version();

        if ( $remote && version_compare( MAP_VERSION, $remote->version, '<' ) ) {
            $transient->response[ $this->plugin_slug ] = (object) [
                'slug'        => dirname( $this->plugin_slug ),
                'new_version' => $remote->version,
                'url'         => $remote->homepage,
                'package'     => $remote->download_url . '?license_key=' . $this->license_key,
                'requires'    => $remote->requires_wp,
                'tested'      => $remote->tested_wp,
            ];
        }

        return $transient;
    }

    private function get_remote_version() {
        $cache_key    = 'map_remote_version';
        $cached       = get_transient( $cache_key );

        if ( $cached !== false ) {
            return $cached;
        }

        $response = wp_remote_get( $this->update_url . '?license_key=' . $this->license_key, [
            'timeout' => 10,
        ]);

        if ( is_wp_error( $response ) ) {
            return false;
        }

        $data = json_decode( wp_remote_retrieve_body( $response ) );
        set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS );

        return $data;
    }
}

Update sunucusu tarafında ne döneceğini de kontrol etmelisiniz. Lisans geçersizse download URL’yi boş döndürün ya da 403 verin; bu sayede süresi dolmuş lisansla güncelleme yapılamaz.

Freemius ile Entegrasyon: Alternatif Yaklaşım

Kendi altyapınızı kurmak istemiyorsanız Freemius SDK’sını entegre etmek çok daha hızlı bir yol:

<?php
if ( ! function_exists( 'my_awesome_plugin_fs' ) ) {
    function my_awesome_plugin_fs() {
        global $my_awesome_plugin_fs;

        if ( ! isset( $my_awesome_plugin_fs ) ) {
            // Freemius SDK'sını dahil et
            require_once dirname( __FILE__ ) . '/freemius/start.php';

            $my_awesome_plugin_fs = fs_dynamic_init([
                'id'                  => '1234',
                'slug'                => 'my-awesome-plugin',
                'type'                => 'plugin',
                'public_key'          => 'pk_your_public_key_here',
                'is_premium'          => true,
                'has_addons'          => false,
                'has_paid_plans'      => true,
                'has_affiliation'     => 'all',
                'menu'                => [
                    'slug'       => 'my-awesome-plugin',
                    'account'    => true,
                    'contact'    => true,
                    'support'    => false,
                ],
            ]);
        }

        return $my_awesome_plugin_fs;
    }

    // Freemius'u başlat
    my_awesome_plugin_fs();
    do_action( 'my_awesome_plugin_fs_loaded' );
}

// Freemius ile premium kontrol
function map_fs_is_premium() {
    return my_awesome_plugin_fs()->can_use_premium_code();
}

Freemius’un güzel yanı şu: Kullanıcı davranış analitiği, opt-in e-posta sistemi, A/B test altyapısı ve otomatik faturalandırma hepsi hazır geliyor. Eklentinizi ilk kez para kazanır hale getiriyorsanız bu yolu tercih etmenizi öneririm.

Gerçek Dünya Senaryosu: WooCommerce Entegrasyonu

Diyelim ki bir envanter yönetim eklentisi geliştirdiniz. Ücretsiz sürümde ürün stok takibi var, premium’da ise otomatik sipariş oluşturma ve tedarikçi entegrasyonu var. Bu mantığı hayata geçirmek için:

<?php
// Ücretsiz: Basit stok uyarısı
add_action( 'woocommerce_product_set_stock', function( $product ) {
    $threshold = get_option( 'map_stock_threshold', 5 );

    if ( $product->get_stock_quantity() <= $threshold ) {
        // Ücretsiz: sadece admin bildirimi
        $admin_email = get_option( 'admin_email' );
        wp_mail(
            $admin_email,
            'Düşük Stok Uyarısı: ' . $product->get_name(),
            $product->get_name() . ' ürününde stok azaldı: ' . $product->get_stock_quantity()
        );

        // Premium: otomatik satın alma emri oluştur
        if ( map_is_premium() ) {
            $premium = new MAP_Premium_Inventory();
            $premium->create_purchase_order( $product );
        }
    }
});

// Premium sınıfı içinde
class MAP_Premium_Inventory {

    public function create_purchase_order( $product ) {
        $supplier_id = get_post_meta( $product->get_id(), '_map_supplier_id', true );

        if ( ! $supplier_id ) {
            return;
        }

        $supplier_data = get_post( $supplier_id );
        $api_endpoint  = get_post_meta( $supplier_id, '_map_api_endpoint', true );
        $reorder_qty   = get_post_meta( $product->get_id(), '_map_reorder_quantity', true ) ?: 50;

        $response = wp_remote_post( $api_endpoint, [
            'body' => [
                'product_sku' => $product->get_sku(),
                'quantity'    => $reorder_qty,
                'callback'    => home_url( '/wp-json/map/v1/order-callback' ),
            ],
        ]);

        if ( ! is_wp_error( $response ) ) {
            $this->log_purchase_order( $product->get_id(), $reorder_qty, $supplier_id );
        }
    }

    private function log_purchase_order( $product_id, $qty, $supplier_id ) {
        global $wpdb;
        $wpdb->insert(
            $wpdb->prefix . 'map_purchase_orders',
            [
                'product_id'  => $product_id,
                'supplier_id' => $supplier_id,
                'quantity'    => $qty,
                'status'      => 'pending',
                'created_at'  => current_time( 'mysql' ),
            ],
            [ '%d', '%d', '%d', '%s', '%s' ]
        );
    }
}

Lisans Sona Erme ve Yetkisiz Kullanım Önlemleri

Lisans süresi dolduğunda ne olmalı? Önerim: Premium özellikler anında kapanmasın. “Grace period” yani 14 günlük ek süre tanıyın. Bu hem kullanıcı dostu hem de churn’ü azaltır.

<?php
function map_is_premium() {
    $status   = get_option( 'map_license_status', 'invalid' );
    $expires  = get_option( 'map_license_expires', '' );

    if ( $status !== 'valid' ) {
        return false;
    }

    if ( empty( $expires ) ) {
        return true; // Süresiz lisans
    }

    $expire_date  = strtotime( $expires );
    $grace_period = 14 * DAY_IN_SECONDS; // 14 gün ek süre

    if ( time() > ( $expire_date + $grace_period ) ) {
        update_option( 'map_license_status', 'expired' );
        return false;
    }

    // Grace period içindeyse uyarı göster ama çalışmaya devam et
    if ( time() > $expire_date ) {
        add_action( 'admin_notices', function() use ( $expires ) {
            echo '<div class="notice notice-warning"><p>';
            echo '<strong>MAP Premium:</strong> Lisansınızın süresi ' . esc_html( date( 'd.m.Y', strtotime( $expires ) ) ) . ' tarihinde doldu. ';
            echo '<a href="' . admin_url( 'options-general.php?page=map-license' ) . '">Yenile</a>';
            echo '</p></div>';
        });
    }

    return true;
}

Domain kısıtlaması da eklemeniz gerekiyor. Lisansı aktive ederken hangi domain için aktive edildiğini sunucuda kaydedin ve doğrulama sırasında get_site_url() ile karşılaştırın. Birden fazla domain için kullanılmasını önlemenin en basit yolu bu.

Dönüşüm Oranını Artırmak için Teknik Detaylar

Admin bildirimlerini doğru kurgulamak dönüşümde ciddi fark yaratıyor. Ama dikkatli olun: Çok agresif bildirimler kötü yorumlara yol açar.

<?php
// Kurulumdan 7 gün sonra, haftada en fazla bir kez göster
add_action( 'admin_notices', function() {
    if ( map_is_premium() ) {
        return;
    }

    $install_date    = get_option( 'map_install_date', time() );
    $last_dismissed  = get_option( 'map_promo_dismissed', 0 );
    $days_since      = ( time() - $install_date ) / DAY_IN_SECONDS;

    // 7 günden az kullanıldıysa veya son kapatmadan 30 gün geçmediyse gösterme
    if ( $days_since < 7 || ( time() - $last_dismissed ) < 30 * DAY_IN_SECONDS ) {
        return;
    }

    // Sadece eklenti sayfalarında göster
    $screen = get_current_screen();
    if ( ! $screen || strpos( $screen->id, 'map' ) === false ) {
        return;
    }

    ?>
    <div class="notice notice-info is-dismissible" id="map-upgrade-notice">
        <p>
            <strong>My Awesome Plugin</strong> kullanıcısısınız!
            Premium sürüme geçerek tedarikçi entegrasyonu ve otomatik raporlama özelliklerine kavuşabilirsiniz.
            <a href="https://yourstore.com/pricing?utm_source=plugin&utm_medium=notice" target="_blank">Detayları gör</a>
        </p>
    </div>
    <script>
    jQuery('#map-upgrade-notice').on('click', '.notice-dismiss', function() {
        jQuery.post(ajaxurl, {
            action: 'map_dismiss_promo',
            nonce: '<?php echo wp_create_nonce( "map_dismiss" ); ?>'
        });
    });
    </script>
    <?php
});

add_action( 'wp_ajax_map_dismiss_promo', function() {
    check_ajax_referer( 'map_dismiss', 'nonce' );
    update_option( 'map_promo_dismissed', time() );
    wp_send_json_success();
});

Sonuç

Freemium model doğru kurgulandığında hem kullanıcı için hem de geliştirici için kazandıran bir yapı. Teknik tarafta en önemli üç nokta şunlar: Lisans doğrulamanın sağlam ama kullanıcıyı cezalandırmayan bir yapıda olması, premium özelliklerin gizlenmek yerine görünür ama kilitli sunulması ve güncelleme mekanizmasının sorunsuz çalışması.

Kendi lisans sunucusu kurmak istemeyenler için Freemius gerçekten iyi bir seçenek; ancak komisyon yapısını ve uzun vadeli bağımlılığı göz önünde bulundurun. Orta ve büyük ölçekli eklentiler için EDD ile kendi altyapınızı kurmak daha mantıklı.

Son olarak şunu söyleyeyim: En iyi freemium strateji teknik açıdan kusursuz olmaktan çok kullanıcının “bu benim için değerli” demesini sağlamaktan geçiyor. Teknik altyapı bunu desteklemek için var. Önce kullanıcının hangi problemi çöztiğinizi netleştirin, sonra o problemin bir sonraki seviyesini premium’a koyun. Geri kalan her şey bu temelin üzerine inşa ediliyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir