Ödeme Modülü Geliştirme

Ödeme yöntemi, ModuleBase'i genişleten ve $isPayment = true ile kayıt olan bir modüldür. Genel modül kuralları için Yeni Modül Oluştur ve kancalar için Kanca Listesi sayfalarına bakın.

Zorunlu özellikler

ÖzellikAçıklama
$isPayment true — ödeme adımında yöntem olarak kaydeder
$paymentMethodId orders.payment_method içinde saklanan benzersiz kimlik (radyo value ile aynı olmalı)
$paymentMethodLabel Vitrinde görünen etiket (ör. Havale / EFT)
$paysBeforeOrder true — sipariş oluşturulmadan önce yönlendirmeli PSP / sanal POS

Görüntüleme kancaları

  • order_payment — ödeme yöntemi seçimi (radyo + açıklama)
  • order_confirmation — sipariş sonrası talimat / banka bilgisi
  • Yönlendirmeli sağlayıcılar için $paysBeforeOrder = true ve gerekirse özel front route (ör. sanalpos / PayTR / ParamPOS tarzı)

processPayment(array $order): array

Ödeme işlendikten sonra tipik dönüş:

return [
    'success'  => true,
    'redirect' => '',   // PSP URL'si (gerekirse)
    'message'  => '',
];
Referanslar: basit havale için bankwire; yönlendirme / 3D dönüş için paytr, parampos veya sanalpos tarzı modüllere bakın.

Callback / bildirim uçları

Sağlayıcı dönüşlerini $apiActions içinde kaydedin ve /api/module.php?m={name}&action=... veya api/callback.php kalıbı üzerinden karşılayın.

CSRF muaf tutulan tipik aksiyon adları:

  • callback
  • notify
  • webhook
  • ipn
  • return
  • 3d-return
Önemli: CSRF muafiyeti, imza / hash doğrulamasını kaldırmaz. Sağlayıcı imzasını her zaman sunucu tarafında doğrulayın.

Güvenlik kontrol listesi

  • İstemciden gelen tutara asla güvenmeyin — sipariş / sepet tutarını sunucudan doğrulayın
  • PSP imzasını, merchant key / hash'i ve sipariş kimliğini doğrulayın
  • Başarılı ödemeyi yalnızca doğrulanmış callback / IPN sonrası işaretleyin
  • PHP 7.4 uyumlu kod yazın (match, birleşik tipler, adlandırılmış argüman yok)
  • SQL için hazırlanmış ifadeler; çıktıda escape

İlgili sayfalar

PHP
<?php

if (!defined('IN_SCRIPT') && !defined('IN_ADMIN')) {
	exit;
}

class DemoPayModule extends ModuleBase
{
	public string $name = 'demo-pay';
	public string $version = '1.0.0';
	public string $displayName = 'Demo Pay';
	public string $description = 'Sample payment module';

	public bool $isPayment = true;
	public string $paymentMethodId = 'demo_pay';
	public string $paymentMethodLabel = 'Demo Pay';
	public bool $paysBeforeOrder = false;

	public array $displayHooks = [
		'order_payment' => 'Checkout payment option',
		'order_confirmation' => 'Thank-you page instructions',
	];

	public array $defaultDisplayHooks = [
		'order_payment',
		'order_confirmation',
	];

	public array $apiActions = [
		'callback' => 'api/callback.php',
	];

	public function processPayment(array $order): array
	{
		// Post-order flow (bankwire-style). Redirect only if needed.
		return [
			'success' => true,
			'redirect' => '',
			'message' => '',
		];
	}

	public function renderDisplayHook(string $hook, array $context = [])
	{
		if ($hook === 'order_payment') {
			return $this->renderFrontTemplate('order_payment', [
				'paymentMethodId' => $this->paymentMethodId,
				'label' => $this->paymentMethodLabel,
				'formData' => isset($context['formData']) ? $context['formData'] : [],
			]);
		}

		if ($hook === 'order_confirmation') {
			return $this->renderFrontTemplate('order_confirmation', [
				'order' => isset($context['order']) ? $context['order'] : [],
			]);
		}

		return null;
	}
}
callback.php — imza doğrulama taslağı
<?php
// modules/demo-pay/api/callback.php — verify PSP signature sketch

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

header('Content-Type: application/json; charset=utf-8');

$merchantKey = (string) Settings::get('DEMO_PAY_MERCHANT_KEY');
$payload = file_get_contents('php://input');
$data = json_decode((string) $payload, true);
if (!is_array($data)) {
	$data = $_POST;
}

$provided = isset($data['hash']) ? (string) $data['hash'] : '';
$orderId = isset($data['merchant_oid']) ? (string) $data['merchant_oid'] : '';
$status = isset($data['status']) ? (string) $data['status'] : '';
$total = isset($data['total_amount']) ? (string) $data['total_amount'] : '';

$expected = base64_encode(hash_hmac(
	'sha256',
	$orderId . $merchantKey . $status . $total,
	$merchantKey,
	true
));

if ($provided === '' || !hash_equals($expected, $provided)) {
	http_response_code(403);
	echo json_encode(['success' => false, 'message' => 'Invalid signature']);
	exit;
}

// Never trust client amount alone — load order and compare server-side totals here.
if ($status !== 'success') {
	echo json_encode(['success' => false, 'message' => 'Payment not successful']);
	exit;
}

echo json_encode(['success' => true]);