Ödeme Geçidi Yazma
Ödeme modülü bir sağlayıcıyı ödeme seçeneğine dönüştürür: ödeme ekranını gösterir, kartı çeker ya da müşteriyi yönlendirir. Sonucu çekirdeğin mahsup ettiği dizi olarak verir.
Genel Bakış
Bir geçit modülü PaymentGatewayModule'ü genişletir ve coremio/modules/Payment/{Ad}/{Ad}.php altında yaşar. 164 modül gelir, dördü aşağıdaki sandbox örneğidir.
Ödeme hattının dört ayağı vardır; modül ikisine sahiptir.
anlık görüntü core, müşterinin gördüğü tam rakamları taşıyan bir `checkouts` satırı yazar
|
ödeme yüzeyi MODULE payment_screen() müşterinin neyle karşılaşacağına karar verir
|
capture / callback MODULE capture($params) çeker ya da callback() sağlayıcının dönüşünü alır
|
settle core settle_checkout() ödemeyi faturalara işler
Modül bir faturayı asla ödendi yapmaz. ['status' => 'successful', ...] döner, kaydı çekirdek işler. Kendiniz yaparsanız çift kayıt oluşur: yinelenen bildirim kodu iki kez çalıştırır.
Ön Koşullar
- Modül iskeleti: dizin, sınıf dosyası,
config.php,lang/. Bkz. Modül Anatomisi. - Dört
Sample*geçidi: çalışan modüller, her biri bir arketip. - Sağlayıcının test hesabı bilgileri; bildirim gönderiyorsa dışarıdan erişilebilir bir alan adı.
- Örnek
Modules::getInstance('Payment', 'Acme')ile alınır,new Acme()ile değil.
Yapı
Acme.php sınıf: extends PaymentGatewayModule
config.php ['meta' => [...], 'settings' => [...]] döndürür
lang/en.php düz bir key => string haritası döndürür, $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
Arketipi önce seçin; hangi metotları bildireceğinizi o belirler. Çekirdek metotları method_exists ile bulur.
| Arketip | Bildirdiğiniz | Müşteri deneyimi | Kopyalanacak örnek |
|---|---|---|---|
| Kendi kart formu | capture() ve standard_card = true | Ödeme sayfasında kart alanları, çekimi siz yaparsınız | SampleMerchant |
| Yönlendirme / barındırılan sayfa | area() ve callback() | Yönlendirme paneli, ardından sağlayıcının kendi sayfası | SampleThirdParty |
| Kart kasası | capture(), card_setup_result(), meta card-storage-supported | Kayıtlı kartlar ve oturum dışı otomatik ödeme | SampleTokenized |
| Yinelenen abonelik | payment_screen() ezmesi, callback(), cancel_subscription() | Tek seferlik öde ya da abone ol, yenilemeleri sağlayıcı çeker | SampleSubscription |
PayTR ezilmiş bir payment_screen()'den iframe gösterir. Iyzico area()'lı bir yönlendirme geçididir. Stripe kart saklar; PayPal tek çekim ile abonelik sunar.
Adım Adım
Modül
Taban kurucu şunları doldurur: $this->config, $this->lang, $this->dir, $this->url, geri dönüş bağlantıları, yetenek ayarları. parent::__construct() çağırın.
class Acme extends PaymentGatewayModule
{
public function __construct()
{
parent::__construct();
// $this->links['callback'] artık bu modülün herkese açık dönüş adresidir,
// get_auth_token() ile imzalanmıştır; onu sağlayıcıya verin.
}
}
Yetenekler
Yetenek ayarları config.php meta bloğundan gelir, taban kurucu okur.
<?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 yapabilir (otomatik ödeme)
'standard-card-form' => false, // ortak kart giriş formunu basar
'embedded-pane' => true, // panel ödeme sayfasının içine gömülebilir
'subscription-mode' => 'amount', // items | amount | fixed, aşağıya bakın
'subscription-anchor' => false, // ileri bir tarihte sözleşme başlatabilir
],
'settings' => [
'commission_rate' => 0,
'force_convert_to' => 0,
'min_amount' => 0, // kabul edilen tutar aralığı; 0 sınırı kapatır
'max_amount' => 0,
'amount_limit_cid' => 0, // iki sınırın yazıldığı para birimi
'accepted_countries' => [],
'unaccepted_countries' => [],
],
];
Ayar Formu
config_fields() bildirin; ayar ekranı kendini kurar. controller_settings() bildirilen her anahtarı ortak alanlarla config['settings'] içine kaydeder: durum, komisyon oranı, zorunlu para birimi, tutar aralığı, ülke listeleri.
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'] ?? '',
],
];
}
Ödeme Ekranı
Yönlendirmeli geçitte area($params) bildirip işaretleme döndürürsünüz. pre_area() $params'ı toplar; payment_screen() sonucu ['mode' => 'html', ...] olarak bildirir. embedded-pane true iken ödeme sayfasına gömülür, false iken ayrı sayfada görünür.
Dönüş
callback() bildirin. Dağıtıcı payments/{Modul}/{auth_token}/callback adresini buraya yönlendirir, sağlayıcının gönderdiğini olduğu gibi bırakır. Checkout'u bulun, imzayı doğrulayın, sonucu tarif edin; dağıtıcı settle_checkout()'u çağırır.
Checkout kimliğini kendiniz taşıyın, genelde LinkGenerator::wQS() ile kurulan dönüş adresine sorgu parametresi olarak. Sahibi checkout kaydından okuyun.
Kart Alıyorsanız Kartı Çekin
capture($params) bildirin. Kartı önce pre_capture() alır; CSRF anahtarını ve kart alanlarını doğrular. Ayrıca checkout'un işlem yapan hesaba ait olduğunu denetler. Sonra taksit planını çözer, kart üst verisini (CVC hariç) saklar. Durum dizisi döndürün; başarıda mahsup hemen yapılır.
Referans
Taban Sınıfın Verdikleri
// İşlemdeki checkout
public function set_checkout($checkout): void;
public function get_checkout($id = 0, $status = '', $type = '', $uid = 0);
public function save_checkout($id = 0, $fields = []): bool;
public function checkout_total(): float; // çekilecek tek doğru tutar
public function checkout_currency(): int; // para birimi id'si, ISO kodu DEĞİL
public function getItems(): array;
// Para birimi
public function cid_convert_code($id = 0); // 4 => "USD"
public function currency($id = 0); // idempotent: girdi id ya da kod, çıktı satır
// Para ve ücretler
public function commission_fee_calculator($amount): float;
public function get_commission_rate();
public function installment_plans(array $bin, float $base, int $cid): array;
public function installment_plan_total(float $base, float $rate): float;
// Kartlar
public function get_stored_card($id = 0, $user_id = 0): array;
public function checkSaveCard(): bool;
public function checkAutoPay(): bool;
public function card_setup_result(): array;
public function generate_card_identification_checkout($pmethod = ''): int;
// Abonelikler
public function checkout_subscribable(): array;
public function set_subscribed_items($arg = []): void;
public function set_subscribed_sources(array $sources = []): void;
// Altyapı
public function get_auth_token(): string;
public function define_function($name = '', $function_name = ''): void;
public function controller_settings($extraFields = []): array;
public function isEnabled(): bool;
public function save_custom_data($data, $checkout_id = 0): void;
public function get_custom_data($checkout_id = 0);
// Çekirdeğin çağırdığı üçlü, ezilebilir
public function payment_screen(): array;
public function pre_area(): string|false;
public function pre_capture(): string;
// Mahsup, sizin için çağrılır. Modül kodundan asla çağırmayın.
public static function settle_checkout(PaymentGatewayModule $module, array $checkout, array $result): array;
cid_convert_code()'dan geçirin.
$this->links'ten kurun.
payments/{Modul}/function/{ad} adresinde tek bir ek uç yayınlar. Alt çizgi kullanın; tire ve nokta alt çizgiye katlanır.
Sizin Bildirdikleriniz
Çekirdek bunları method_exists ile yoklar. Tabanda yalnız card_setup_result() vardır, varsayılanı ['status' => 'unsupported']: o bir ezme, kalanı eklemedir.
public function capture($params = []); // kart çekimi, durum dizisi döner
public function area($params = []); // yönlendirme geçidi, işaretleme döner
public function callback(); // sağlayıcı dönüşü, durum dizisi döner
public function bin_check($number); // yerel BIN tablosu, kart üst verisi döner
public function config_fields(); // yönetici ayar alanları
public function refundInvoice($invoice = []); // iade, bool döner
public function card_setup_result(): array; // EZME: kasaya alma koşusunun 3-D dönüşü
// Sanal test modülleri tek parametre bildirir, ama çekirdek bunu İKİ parametreyle
// çağırır: planları tutara bağlı olan bir sağlayıcı sorabilsin diye çekim tabanı da geçilir.
public function installment_rates($bin = [], $base = 0); // [adet => vade farkı yüzdesi]
// Abonelikler
public function cancel_subscription($params = []): bool;
public function get_subscription($params = []): array|false;
public function change_subscription_fee($params = [], $value = 0, $currency = 0): bool;
public function remove_subscription_item($params = []): bool; // 'items' modu: tek satırı düşür
$params İçindeki Anahtarlar
İkisi de V3 sözleşmesinin üst kümesini alır. area() altı anahtar alır: checkout_id, amount, currency, currency_id, clientInfo, items. Kalanı, id aynası dahil, yalnız capture'dadır.
id olarak da aynalanır.
"TRY". Bunu int'e cast etmek taşımanın en sık hatasıdır.
amount'a tamamlanmazlar.
user_data dahil checkout veri bloğu. Yalnız capture'da.
bin_check()'inizden veya saklı kart satırından. Yalnız capture'da.
token ve ln4 dahil.
Ne Döndürürsünüz
| Metot | Anahtar | Anlamı |
|---|---|---|
capture() | status | Mahsup edilenler: successful, success, paid, pending, papproval. Tarayıcıya geri verilenler: redirect, 3d, output. Gerisi error sayılır |
redirect | 3-D ya da ek doğrulama için tarayıcının gideceği adres. Yalnız redirect ve 3d durumlarında okunur | |
message | Başarıda etiket => değer haritası, hatada bir cümle | |
card | Token'lanmış kart paketi; müşteri saklamak istediyse kasaya verilir | |
output | Tam sayfa basılacak ham işaretleme; kendini gönderen banka formları için. Yalnız output ve 3d durumlarında okunur | |
callback() | status | successful, pending ya da error |
message | Sağlayıcı referansını Transaction ID altına koyun; mahsup tekilleştirmeyi bununla yapar | |
paid | ['amount' => float, 'currency' => int]; farklıysa gerçekten çekilen tutar | |
callback_message | Yönlendirme yerine aynen yazılır; onay dizesi bekleyen sağlayıcılar için | |
payment_screen() | mode | card, html, redirect, choices, legacy, none ya da error |
html | Sunucuda üretilen işaretleme; kaçışsız gösterilir | |
redirect | Tek yönlendirme butonunun hedefi | |
choices | ['label' => string, 'url' => string, 'image' => string] satırları; PayPal tek seferlik ödemeyi aboneliğin yanında böyle sunar. note üstlerinde görünür, error modu ise onun yerine message taşır |
Tabandaki payment_screen() modu bildirdiğiniz metotlardan türetir. Sağlayıcı kendi işaretlemesini gerektiriyorsa ezin.
Komisyon
Komisyon faturanın başlık alanıdır, kalem değildir. Operatör geçit başına commission_rate belirler, çekirdek yeniden hesaplamada fiyatlar. checkout_total() ücreti içerir, commission_fee_calculator() gösterim içindir.
$base = $this->checkout_total();
$fee = $this->commission_fee_calculator($base); // oran config['settings']['commission_rate']'ten okunur
$note = Money::formatter_symbol($fee, $this->checkout_currency());
Tutar Aralığı
Geçidin sunulup sunulmayacağına üç ortak ayar karar verir; taban bunları config['settings'] içine yazar.
0 alt sınırı kapatır.
0 üst sınırı kapatır. Minimumun altında bir maksimum kayıtta geri çevrilir.
Checkout::payment_methods($ucid, ['amount' => $total]) iki sınırı da ödemenin para birimine çevirir; aralığı tutarı kapsamayan geçit listeden düşer. Ödeme sayfası, fatura, toplu ödeme ve bakiye yükleme aynı çağrıdan geçer.
Karşılaştırılan tutar, geçidin komisyonu ve taksit farkı eklenmeden önceki toplamdır. Aralığı capture() içinde yeniden denetlemeyin: oradaki tutar ücreti çoktan taşır.
Alanı ayar formundan çıkaran modül ('unusedFields' => ['amount_limits']) iki anahtarı da göndermez, saklanan aralık kalır. Tutarı bilmeyen sayfa sınırları kayıt satırından okur.
Örnek
Eksiksiz bir yönlendirme geçidi: panel sağlayıcının çağıracağı adresi kurar, geri dönüş o checkout'u bulur.
class Acme extends PaymentGatewayModule
{
public function area($params = [])
{
$cid = (int) ($params['currency_id'] ?? 0);
// Sağlayıcının çağıracağı dönüş adresi. Dağıtıcının onu kabul etmesini
// $this->links['callback'] içindeki kimlik jetonu sağlar; custom_id ise
// geri dönüşün bu checkout'u oturumsuz bulma yoludur.
$return = LinkGenerator::wQS($this->links['callback'], [
'custom_id' => (int) ($params['checkout_id'] ?? $this->checkout_id),
]);
$session = $this->open_provider_session([
'merchant' => $this->config['settings']['merchant_id'] ?? '',
'amount' => $params['amount'] ?? 0,
'currency' => $params['currency'] ?? '', // ISO kodu, çevrilmiş hâlde
'reference' => 'chk_' . (int) $this->checkout_id,
'return_url' => $return,
]);
if (($session['url'] ?? '') === '')
return ''; // boş bir dönüş, payment_screen()'in "error" modu bildirmesine yol açar
$label = htmlspecialchars((string) ($this->lang['pay-button'] ?? 'Continue'), ENT_QUOTES);
$target = htmlspecialchars($session['url'], ENT_QUOTES);
$amount = htmlspecialchars(Money::formatter_symbol((float) ($params['amount'] ?? 0), $cid), ENT_QUOTES);
return '<div class="d-grid">'
. '<span class="price-chip num-tabular mb-2">' . $amount . '</span>'
. '<a class="btn btn-primary btn-lg" href="' . $target . '">' . $label . '</a>'
. '</div>';
}
}
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"];
// set_checkout(), $this->checkout, $this->checkout_id ve müşteri bilgisini
// doldurur; böylece checkout_total() ve checkout_currency() aşağıda yanıt verir.
$this->set_checkout($checkout);
$reference = (string) Filter::init("REQUEST/reference", "letters_numbers");
$signature = (string) Filter::init("REQUEST/signature", "letters_numbers");
// Hiçbir şeye güvenmeden ÖNCE doğrulayın: geri dönüş adresi herkese açıktır.
if (!$this->signature_matches($reference, $signature))
return ['status' => "error", 'checkout_id' => $checkout_id, 'message' => $this->pay_lang("error-verification")];
$remote = $this->fetch_provider_charge($reference);
if (($remote['state'] ?? '') !== 'captured')
return [
'status' => "error",
'checkout_id' => $checkout_id,
'message' => (string) ($remote['reason'] ?? ($this->lang['error-declined'] ?? 'Declined.')),
];
return [
'status' => "successful",
'checkout_id' => $checkout_id,
'message' => ['Transaction ID' => $reference],
// Anlık görüntüden farklıysa GERÇEKTEN çekilen tutarı bildirin, örneğin
// sağlayıcının sayfasında bir taksit planı seçildikten sonra.
'paid' => [
'amount' => (float) ($remote['amount'] ?? $this->checkout_total()),
'currency' => $this->checkout_currency(),
],
];
}
// coremio/classes/PaymentGatewayModule.php, settle_checkout()
$result = $module->callback();
// 1. zaten ödendi mi? -> kısa devre, ikinci kayıt yok
// 2. durum successful değil -> logla, müşteriyi links['failed']'a gönder
// 3. ertelenmiş sipariş mi? -> siparişi ve faturayı şablondan kur
// 4. anlık görüntüden fazlası mı çekildi? -> önce taksit vade farkını kaydet
// 5. her faturayı kaydet:
foreach (\Checkout::invoice_ids($fresh) as $invoiceId) {
$due = round((float) Invoices::balance($invoiceId), 2);
if ($due <= 0.005) continue;
Invoices::add_payment($invoiceId, [
'amount' => $due,
'currency' => (int) (Invoices::get($invoiceId, ['select' => "currency"])["currency"] ?? 0),
'pmethod' => $module->name,
'transaction_id' => $tx, // message['Transaction ID']'den gelir
'description' => $module->lang["name"] ?? $module->name,
]);
}
Tam ödeme faturayı ödendiye çevirir. Bağlı sipariş aktifleşir, hizmet sağlanır, gelir satırı yazılır, bildirim gider.
Tuzaklar
(int) $params['currency'], sağlayıcının USD beklediği yere 4 gibi anlamsız bir değer koyar. Id için currency_id'yi okuyun.
Kalem toplamları nettir; data.tax anahtarı ve amount_including_discount yoktur, ikisi de V3'tü. Kalem dökümü için kalanı bir "vergi ve ücretler" satırıyla checkout_total()'a mutabık kılın.
Modüller o dalı is_array($params['card_storage'] ?? null) ile kapılar; yeni kartta anahtar yoktur. Varsayılanı [] yapmayın.
callback_message döndürün, dağıtıcı onu yazar. Makine çağırana yönlendirme verirseniz sağlayıcı bildirimi başarısız sayar ve yeniden dener.
capture()'a geçer ve unutulur. Onu checkout verisine ya da kasaya yazmak uyumluluk ihlalidir; saklı kart yalnız sağlayıcı token'ını ve görüntüleme üst verisini tutar.
Sözleşme ['status' => 'error', 'message' => ...]'dır; mahsup bunu loglar ve gösterir. $this->error atayıp false döndürmek eski bir yedek yoldur, ancak dizi boşsa okunur.
İ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.