WISECP'te ödeme geçidi modülü yazmak, coremio/modules/Payment/{Ad}/{Ad}.php altında PaymentGatewayModule sınıfını genişleten bir sınıf, bir config.php ve lang/ dil dosyaları hazırlamak demektir. Modül müşterinin ödeme ekranında ne göreceğine karar verir, kartı çeker ya da müşteriyi sağlayıcıya yönlendirir ve sonucu bir durum dizisi olarak çekirdeğe bildirir. Faturayı ödendi yapan ise çekirdektir; modül bunu asla kendisi yapmaz.

Bu rehberde belgelenen dört arketipi, zorunlu ve isteğe bağlı metotları, config.php yeteneklerini, yönlendirmeli bir geçidin ödeme ekranı ve geri dönüş (callback) akışını ve mahsubun (settle) nasıl çalıştığını anlatıyoruz. Modül iskeleti ve genel kurallar için önce WISECP modül geliştirme rehberi yazısına, panelde ödeme ayarları için WISECP fatura, ödeme ve vergi ayarları yazısına bakın.

Ödeme hattının dört ayağı

Ödeme hattının dört ayağı: Anlık görüntü, Ödeme yüzeyi, Çekim / geri dönüş, Mahsup

Belge ödeme akışını dört adımda anlatır; modül bunlardan ikisinin sahibidir:

AdımSahibiNe olur
Anlık görüntüÇekirdekMüşterinin gördüğü tam rakamları taşıyan bir checkouts satırı yazılır
Ödeme yüzeyiModülpayment_screen() müşterinin neyle karşılaşacağına karar verir
Çekim / geri dönüşModülcapture($params) kartı çeker ya da callback() sağlayıcının dönüşünü alır
MahsupÇekirdeksettle_checkout() ödemeyi faturalara işler

Önce arketipi seçin

Çekirdek modülün metotlarını method_exists ile bulur; hangi metotları bildireceğinizi seçtiğiniz arketip belirler. WISECP dört çalışan Sample* geçidini örnek olarak verir:

ArketipBildirdiğinizMüşteri deneyimiÖrnek
Kendi kart formucapture() ve standart kart formuÖdeme sayfasında kart alanları, çekimi modül yaparSampleMerchant
Yönlendirme / barındırılan sayfaarea() ve callback()Yönlendirme paneli, ardından sağlayıcının sayfasıSampleThirdParty
Kart kasasıcapture(), card_setup_result(), card-storage-supportedKayıtlı kartlar ve oturum dışı otomatik ödemeSampleTokenized
Yinelenen abonelikpayment_screen() ezmesi, callback(), cancel_subscription()Tek seferlik ödeme ya da abonelikSampleSubscription

Bir sağlayıcının hangi arketipe uyduğunu onun API belgesi söyler. Örneğin kendi sitenizde kart bilgisi almadan ödeme formu ya da 3D Secure yönlendirmesi sunan sağlayıcılar genelde yönlendirme arketipine yakındır. Sağlayıcı API'lerini anlatan iyzico ödeme entegrasyonu ve Garanti BBVA sanal POS API yazılarımız bu açıdan fikir verebilir; bu yazılar sağlayıcıların kendi API'lerini anlatır, birer WISECP modülü değildir.

Dizin yapısı ve config.php

text
coremio/modules/Payment/Acme/
├── Acme.php        sınıf: extends PaymentGatewayModule
├── config.php      ['meta' => [...], 'settings' => [...]] döndürür
├── lang/en.php     key => string haritası, $this->lang['key'] ile okunur
├── lang/tr.php
└── logo.png        admin modül listesinde ve ödeme yöntemi satırında görünür

Modül anatomisi belgesine göre birbirine uyması gereken üç ad vardır: dizin adı, sınıf dosyası adı (dizin adı artı .php) ve sınıf adı. Harfe duyarlı dosya sistemlerinde büyük küçük harf farkı önemlidir. lang/en.php pratikte zorunludur, çünkü etkin dilin dosyası yoksa ona düşülür. Örnek, new Acme() ile değil Modules::getInstance('Payment', 'Acme') ile alınır.

Yetenekler config.php içindeki meta bloğundan okunur; ortak ayarlar settings içinde durur:

php
<?php
return [
    'meta' => [
        'name'                   => 'Acme Pay',
        'version'                => '1.0',
        'icon_type'              => 'font',
        'icon'                   => 'bi bi-credit-card',
        'card-storage-supported' => false,  // kart jetonunu kasaya alabilir
        'auto-payment-supported' => false,  // oturum dışı çekim (otomatik ödeme)
        'standard-card-form'     => false,  // ortak kart giriş formunu basar
        'embedded-pane'          => true,   // ödeme sayfasının içine gömülebilir
        'subscription-mode'      => 'amount',  // items | amount | fixed
        'subscription-anchor'    => false,
    ],
    'settings' => [
        'commission_rate'      => 0,
        'force_convert_to'     => 0,
        'min_amount'           => 0,   // 0 sınırı kapatır
        'max_amount'           => 0,
        'amount_limit_cid'     => 0,
        'accepted_countries'   => [],
        'unaccepted_countries' => [],
    ],
];

Ayar formu: config_fields()

config_fields() metodunu bildirdiğinizde ayar ekranı kendini kurar. Taban sınıfın controller_settings() metodu bildirdiğiniz her anahtarı, durum, komisyon oranı, zorunlu para birimi, tutar aralığı ve ülke listeleri gibi ortak alanlarla birlikte config['settings'] içine kaydeder.

php
public function config_fields()
{
    return [
        'merchant_id' => [
            'name'        => $this->lang['merchant-id'] ?? 'Merchant ID',
            'description' => $this->lang['merchant-id-desc'] ?? '',
            'type'        => 'text',
            'value'       => $this->config['settings']['merchant_id'] ?? '',
            'placeholder' => 'acme_12345',
        ],
        'secret_key' => [
            'name'  => $this->lang['secret-key'] ?? 'Secret Key',
            'type'  => 'password',
            'value' => $this->config['settings']['secret_key'] ?? '',
        ],
    ];
}

Operatör tarafında bu alanlar Ayarlar > Finansal > Ödeme Ağ Geçitleri ekranında ({admin}/modules/payment) Tüm Modüller altındaki geçit kartında görünür. Geçidin müşteriye sunulması için Başlangıç Ayarları sekmesindeki Varsayılan Modüller alanında seçilmesi gerekir. Panel belgesi her geçidin ortak ayarlarını Komisyon Oranı (%), Para Birimi Dönüştürme, Kabul Edilen Tutar Aralığı ve Kabul Edilen / Edilmeyen Ülkeler olarak sayar; ekran MODULES_PAYMENT_SETTINGS yetkisi ister.

Metotlar: taban sınıfın verdikleri ve sizin bildirdikleriniz

Metotlar: taban sınıfın verdikleri ve sizin bildirdikleriniz: checkout_total(), checkout_currency(), cid_convert_code($id), get_checkout($id) / set_checkout($checkout)

Taban sınıf checkout, para birimi, komisyon, kart ve abonelik yardımcıları sağlar. En sık kullanacaklarınız:

MetotNe işe yarar
checkout_total()Sağlayıcıya gönderilecek tek doğru tutar; vergi, komisyon ve taksit farkını içerir
checkout_currency()İç para birimi id değeri; ISO kodu değil
cid_convert_code($id)Para birimi id değerini ISO koduna çevirir (4 ise USD gibi)
get_checkout($id) / set_checkout($checkout)Checkout satırını okur ve modüle yükler
get_auth_token()Geri dönüş adresindeki imza parçası
commission_fee_calculator($amount)Komisyonu gösterim için hesaplar
save_custom_data() / get_custom_data()Checkout başına kendi verinizi saklar

Çekirdeğin method_exists ile yokladığı, sizin bildirdiğiniz metotlar şunlardır: capture($params), area($params), callback(), bin_check($number), config_fields(), iade için refundInvoice($invoice), kasa için card_setup_result() ezmesi, installment_rates($bin, $base) ve abonelik metotları (cancel_subscription, get_subscription, change_subscription_fee, remove_subscription_item). Belge önemli bir ayrıntıyı not eder: installment_rates çekirdek tarafından iki parametreyle çağrılır.

settle_checkout() ise mahsup metodudur ve sizin için çağrılır; modül kodundan asla çağırmayın.

Yönlendirmeli geçit: area() ve callback()

Yönlendirme arketipinde area($params) müşteriye gösterilecek işaretlemeyi döndürür. area() altı anahtar alır: checkout_id, amount, currency, currency_id, clientInfo ve items. Geri dönüş adresi payments/{Modul}/{auth_token}/callback biçimindedir ve $this->links['callback'] içinde imzalanmış olarak hazırdır. Geri dönüşte oturum olmadığı için checkout numarasını adrese kendiniz eklersiniz:

php
<?php
class Acme extends PaymentGatewayModule
{
    public function __construct()
    {
        parent::__construct();   // config, lang, dir, url ve links doldurulur
    }

    public function area($params = [])
    {
        $return = LinkGenerator::wQS($this->links['callback'], [
            'custom_id' => (int) ($params['checkout_id'] ?? $this->checkout_id),
        ]);

        // open_provider_session sizin yazacağınız özel metottur: sağlayıcının
        // ödeme oturumu açma API'sini çağırır ve ödeme sayfası adresini döndürür.
        $session = $this->open_provider_session([
            'merchant'   => $this->config['settings']['merchant_id'] ?? '',
            'amount'     => $params['amount'] ?? 0,
            'currency'   => $params['currency'] ?? '',   // ISO kodu, ör. "TRY"
            'reference'  => 'chk_' . (int) $this->checkout_id,
            'return_url' => $return,
        ]);

        if (($session['url'] ?? '') === '')
            return '';   // boş dönüş, payment_screen() tarafında "error" moduna yol açar

        $label  = htmlspecialchars((string) ($this->lang['pay-button'] ?? 'Continue'), ENT_QUOTES);
        $target = htmlspecialchars($session['url'], ENT_QUOTES);
        return '<a class="btn btn-primary btn-lg" href="' . $target . '">' . $label . '</a>';
    }
}

Geri dönüş tarafında sıralama önemlidir: önce checkout'u bulun, sonra imzayı doğrulayın, sonra sağlayıcıdan işlemin gerçek durumunu okuyun. Geri dönüş adresi herkese açıktır; doğrulamadan hiçbir değere güvenmeyin.

php
public function callback()
{
    $checkout_id = (int) Filter::init("REQUEST/custom_id", "numbers");
    $checkout    = $checkout_id ? $this->get_checkout($checkout_id) : false;
    if (!$checkout)
        return ['status' => "error", 'message' => "checkout-not-found"];

    $this->set_checkout($checkout);   // checkout_total() ve checkout_currency() artık yanıt verir

    $reference = (string) Filter::init("REQUEST/reference", "letters_numbers");
    $signature = (string) Filter::init("REQUEST/signature", "letters_numbers");

    // signature_matches ve fetch_provider_charge sizin özel metotlarınızdır;
    // imza yöntemi sağlayıcının belgesine göre yazılır.
    if (!$this->signature_matches($reference, $signature))
        return ['status' => "error", 'checkout_id' => $checkout_id, 'message' => 'verification failed'];

    $remote = $this->fetch_provider_charge($reference);
    if (($remote['state'] ?? '') !== 'captured')
        return ['status' => "error", 'checkout_id' => $checkout_id, 'message' => 'declined'];

    return [
        'status'      => "successful",
        'checkout_id' => $checkout_id,
        'message'     => ['Transaction ID' => $reference],   // mahsup bununla tekilleştirir
        'paid'        => [
            'amount'   => (float) ($remote['amount'] ?? $this->checkout_total()),
            'currency' => $this->checkout_currency(),
        ],
    ];
}

callback() için geçerli durumlar successful, pending ve error'dur. Sağlayıcı sunucudan sunucuya bildirim gönderiyor ve yanıt olarak belirli bir onay metni bekliyorsa callback_message döndürün; dağıtıcı onu yönlendirme yerine aynen yazar. Makine çağırana yönlendirme verirseniz sağlayıcı bildirimi başarısız sayıp yeniden dener.

Kart alan geçit: capture()

Kart alan geçit: capture(): successful, success, paid, pending, papproval, redirect, 3d, output, diğer her şey

Kendi kart formu arketipinde capture($params) bildirirsiniz. Kart önce pre_capture()'dan geçer: CSRF anahtarı ve kart alanları doğrulanır, checkout'un işlem yapan hesaba ait olduğu denetlenir, taksit planı çözülür. capture() içinde $params ek olarak installment (sunucunun onayladığı taksit adedi), yeni kart için num, holder_name, expiry_m, expiry_y, cvc ve saklı kart çekiliyorsa card_storage taşır.

capture() status değeriSonuç
successful, success, paid, pending, papprovalMahsup edilir
redirect, 3dTarayıcı redirect adresine gönderilir (3D ya da ek doğrulama)
outputoutput alanındaki ham işaretleme tam sayfa basılır (kendini gönderen banka formları)
diğer her şeyerror sayılır; message alanı bir cümle olmalı

Mahsup ve mutabakat

Modül successful döndürdüğünde çekirdek settle_checkout()'u çalıştırır. Belgedeki özete göre mahsup sırası şöyledir: checkout zaten ödendiyse kısa devre yapar ve ikinci kayıt oluşturmaz; durum başarılı değilse kaydeder ve müşteriyi başarısız ödeme sayfasına gönderir; ertelenmiş bir siparişse siparişi ve faturayı kurar; anlık görüntüden fazla çekildiyse önce taksit vade farkını kaydeder; sonra her faturaya kalan bakiye kadar ödeme ekler. Ödeme kaydındaki transaction_id, sizin message['Transaction ID'] değerinizden gelir.

Bu tasarımın mutabakat açısından anlamı şudur: settle_checkout() idempotenttir, işlem numarasına göre tekilleştirir, dolayısıyla sağlayıcının aynı bildirimi iki kez göndermesi zararsızdır. Sizin göreviniz doğru işlem numarasını ve gerçekten çekilen tutarı (paid) bildirmektir. Tam ödeme faturayı ödendiye çevirir; bağlı sipariş aktifleşir, hizmet sağlanır, gelir satırı yazılır ve bildirim gider.

Ödeme sonrası kendi sisteminize veri aktarmanız gerekiyorsa bunu modülün içinde değil bir kanca dinleyicisiyle yapın. WISECP API belgesi, ödendi geçişi dahil bir fatura her durum değiştirdiğinde çalışan action:invoice.status_changed kancasını örnek verir. Kancalar için WISECP hook kullanımı yazısına bakın.

Komisyon ve tutar aralığı

Komisyon faturanın kalemi değil başlık alanıdır. Operatör geçit başına commission_rate belirler, çekirdek yeniden hesaplamada fiyatlar; checkout_total() ücreti zaten içerir. min_amount ve max_amount geçidin sunulup sunulmayacağına karar verir (0 sınırı kapatır), amount_limit_cid sınırların yazıldığı para birimidir. Karşılaştırılan tutar komisyon ve taksit farkı eklenmeden önceki toplamdır; belge bu yüzden aralığın capture() içinde yeniden denetlenmemesini söyler.

Sık yapılan hatalar

  • $params['currency'] sayı değildir: ISO kodudur ("TRY"). (int) ile çevirmek sağlayıcıya anlamsız bir değer gönderir; id için currency_id okuyun.
  • Kalem toplamları tutara eşit değildir: items satırları nettir. Kalem dökümü gönderiyorsanız farkı "vergi ve ücretler" satırıyla checkout_total()'a tamamlayın.
  • Boş card_storage dizisi: saklı kart yoksa anahtar hiç yoktur. Varsayılanı [] yaparsanız kod "saklı kart var" dalına girer; is_array($params['card_storage'] ?? null) kullanın.
  • Hatayı dönüş dizisiyle bildirin: sözleşme ['status' => 'error', 'message' => ...] biçimidir. $this->error atayıp false döndürmek eski bir yedek yoldur.
  • Faturayı kendiniz ödendi yapmayın: çift kayıt oluşur; mahsup çekirdeğin işidir.
  • Ad alanında çıplak sınıf adı: modülü WISECP\Modules\Payment ad alanında yazarsanız çekirdek sınıfları içe aktarın ya da başlarına ters bölü koyun.

Sık Sorulan Sorular

WISECP ödeme modülü hangi dizine konur?

coremio/modules/Payment/{Ad}/ altına. Sınıf dosyası dizinle aynı adı taşır (Acme/Acme.php) ve PaymentGatewayModule'ü genişletir.

Ödeme başarılı olunca faturayı nasıl ödendi yaparım?

Yapmazsınız. capture() ya da callback() içinden status değeri successful olan bir dizi ve Transaction ID döndürürsünüz; çekirdek settle_checkout() ile ödemeyi faturalara işler.

Sağlayıcı aynı bildirimi iki kez gönderirse ne olur?

Belgeye göre mahsup idempotenttir: checkout zaten ödendiyse kısa devre yapar ve işlem numarasına göre tekilleştirir. İkinci bildirim ikinci ödeme kaydı oluşturmaz.

Geri dönüş adresini sağlayıcıya nasıl veririm?

$this->links['callback'] değerini LinkGenerator::wQS() ile checkout numarasını ekleyerek kullanın. Adres get_auth_token() ile imzalıdır; imzasız çağrılar reddedilir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: WISECP nedir · WISECP modül geliştirme · WISECP sunucu modülü yazma · WISECP fatura ve ödeme ayarları · WISECP hook kullanımı · iyzico entegrasyonu · Garanti BBVA sanal POS API