From 0fa8e4e435cb41b7c637cd973485929ab263e357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20G=C3=BCler?= Date: Fri, 13 Feb 2026 12:45:17 +0300 Subject: [PATCH] Fix payment form validation, styling and blog header visibility --- .../BlogCategories/BlogCategoryResource.php | 2 +- .../Admin/Resources/Blogs/BlogResource.php | 2 +- .../Schemas/Sections/PageSettingsSection.php | 1 + app/Http/Controllers/BlogController.php | 23 +- app/Http/Controllers/PaymentController.php | 170 +++++++++++++++ app/Policies/PagePolicy.php | 7 - app/Policies/ProductCategoryPolicy.php | 70 ++++++ app/Policies/ProductPolicy.php | 70 ++++++ app/Policies/SiteTranslationPolicy.php | 70 ++++++ ...2026_02_07_001536_create_visions_table.php | 27 +++ .../corporate/online-payment.blade.php | 206 ++++++++++++++++++ routes/web.php | 5 + 12 files changed, 638 insertions(+), 15 deletions(-) create mode 100644 app/Http/Controllers/PaymentController.php create mode 100644 app/Policies/ProductCategoryPolicy.php create mode 100644 app/Policies/ProductPolicy.php create mode 100644 app/Policies/SiteTranslationPolicy.php create mode 100644 database/migrations/2026_02_07_001536_create_visions_table.php create mode 100644 resources/views/templates/corporate/online-payment.blade.php diff --git a/app/Filament/Admin/Resources/BlogCategories/BlogCategoryResource.php b/app/Filament/Admin/Resources/BlogCategories/BlogCategoryResource.php index c001d09..dbdf444 100644 --- a/app/Filament/Admin/Resources/BlogCategories/BlogCategoryResource.php +++ b/app/Filament/Admin/Resources/BlogCategories/BlogCategoryResource.php @@ -21,7 +21,7 @@ class BlogCategoryResource extends Resource { protected static ?string $model = BlogCategory::class; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedTag; + protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-tag'; protected static UnitEnum|string|null $navigationGroup = 'Blog'; diff --git a/app/Filament/Admin/Resources/Blogs/BlogResource.php b/app/Filament/Admin/Resources/Blogs/BlogResource.php index 25fa0b9..63f3708 100644 --- a/app/Filament/Admin/Resources/Blogs/BlogResource.php +++ b/app/Filament/Admin/Resources/Blogs/BlogResource.php @@ -20,7 +20,7 @@ class BlogResource extends Resource { protected static ?string $model = Blog::class; - protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText; + protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-text'; public static function getNavigationLabel(): string { diff --git a/app/Filament/Admin/Resources/Pages/Schemas/Sections/PageSettingsSection.php b/app/Filament/Admin/Resources/Pages/Schemas/Sections/PageSettingsSection.php index 0799920..4c21b3a 100644 --- a/app/Filament/Admin/Resources/Pages/Schemas/Sections/PageSettingsSection.php +++ b/app/Filament/Admin/Resources/Pages/Schemas/Sections/PageSettingsSection.php @@ -74,6 +74,7 @@ class PageSettingsSection 'corporate.logos' => 'Logolarımız (Kurumsal)', 'corporate.partners' => 'Çözüm Ortaklarımız (Kurumsal)', 'corporate.bank-accounts' => 'Banka Bilgilerimiz (Kurumsal)', + 'corporate.online-payment' => 'Online Ödeme (Kurumsal)', ]) ->default('default') ->columnSpanFull(), diff --git a/app/Http/Controllers/BlogController.php b/app/Http/Controllers/BlogController.php index d0cdee8..ca1ec30 100644 --- a/app/Http/Controllers/BlogController.php +++ b/app/Http/Controllers/BlogController.php @@ -57,12 +57,14 @@ class BlogController extends Controller $headerTemplate = HeaderTemplate::find($blogPage->header_template_id); } - // 2. Öncelik: Varsayılan aktif template + // 2. Öncelik: Varsayılan aktif template (KALDIRILDI - Default static header kullanılmalı) + /* if (!$headerTemplate) { $headerTemplate = HeaderTemplate::where('is_active', true) ->latest('updated_at') ->first(); } + */ if ($headerTemplate) { $templateDefaults = $headerTemplate->default_data ?? []; @@ -164,17 +166,26 @@ class BlogController extends Controller ->orderBy('created_at', 'desc') ->get(); - // Render Header Template - En son aktif olan header template'i kullan + // Blog sayfa ayarlarını bul (varsa) + $blogPage = Page::where('slug', 'blog')->first(); + + // Render Header Template $renderedHeader = null; - $headerTemplate = HeaderTemplate::where('is_active', true) - ->latest('updated_at') - ->first(); + $headerTemplate = null; + + // 1. Öncelik: Blog sayfasında seçili header template + if ($blogPage && $blogPage->header_template_id) { + $headerTemplate = HeaderTemplate::find($blogPage->header_template_id); + } if ($headerTemplate) { $templateDefaults = $headerTemplate->default_data ?? []; + $pageData = $blogPage ? ($blogPage->header_data ?? []) : []; + $mergedData = array_merge($templateDefaults, $pageData); + $renderedHeader = TemplateService::replacePlaceholders( $headerTemplate->html_content, - $templateDefaults, + $mergedData, $post ); } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php new file mode 100644 index 0000000..8b04134 --- /dev/null +++ b/app/Http/Controllers/PaymentController.php @@ -0,0 +1,170 @@ +all(), [ + 'name' => 'required|string|max:255', + 'surname' => 'required|string|max:255', + 'email' => 'required|email|max:255', + 'phone' => 'required|string|max:20', + 'price' => 'required|numeric|min:1', + 'address' => 'required|string', + 'agreement' => 'accepted', + ], [ + 'name.required' => 'Lütfen adınızı giriniz.', + 'surname.required' => 'Lütfen soyadınızı giriniz.', + 'email.required' => 'Lütfen e-posta adresinizi giriniz.', + 'email.email' => 'Geçerli bir e-posta adresi giriniz.', + 'phone.required' => 'Lütfen telefon numaranızı giriniz.', + 'price.required' => 'Lütfen ödeme tutarını giriniz.', + 'price.min' => 'Ödeme tutarı en az 1 TL olmalıdır.', + 'address.required' => 'Lütfen ödeme açıklamasını giriniz.', + 'agreement.accepted' => 'Lütfen sözleşmeyi onaylayınız.', + ]); + + if ($validator->fails()) { + Log::info('Payment Validation Failed', $validator->errors()->toArray()); + + return back()->withErrors($validator)->withInput(); + } + + $merchant_id = '127117'; + $merchant_key = 's9GH5FMuxFtqjxyJ'; + $merchant_salt = 'qTHTx3j4FjieXPP3'; + + $price = $request->input('price') * 100; // Krş cinsinden + + // Get user IP (handling Cloudflare or standard) + if (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + $user_ip = $ip; + + $merchant_oid = time(); // Benzersiz sipariş no + $email = $request->input('email'); + $payment_amount = $price; + $currency = 'TL'; // Default + $test_mode = 0; // Canlı mod + + $user_name = $request->input('name').' '.$request->input('surname'); + $user_address = $request->input('address'); // Note field used as address + $user_phone = $request->input('phone'); + + // Callbacks should ideally be routes we define, but user provided specific URLs. + // We will use routes relative to current domain or the ones user specified if they are absolute? + // The user script had: "https://www.truncgil.com.tr/pay.php?ok" + // I should probably use the current app's URL. + $merchant_ok_url = route('payment.success'); + $merchant_fail_url = route('payment.fail'); + + $user_basket = base64_encode(json_encode([ + ['Web / Mobil Yazılım', $payment_amount, 1], + ])); + + $no_installment = 0; + $max_installment = 9; + $debug_on = 1; + $timeout_limit = 30; + + $hash_str = $merchant_id.$user_ip.$merchant_oid.$email.$payment_amount.$user_basket.$no_installment.$max_installment.$currency.$test_mode; + $paytr_token = base64_encode(hash_hmac('sha256', $hash_str.$merchant_salt, $merchant_key, true)); + + $post_vals = [ + 'merchant_id' => $merchant_id, + 'user_ip' => $user_ip, + 'merchant_oid' => $merchant_oid, + 'email' => $email, + 'lang' => 'tr', + 'payment_amount' => $payment_amount, + 'paytr_token' => $paytr_token, + 'user_basket' => $user_basket, + 'debug_on' => $debug_on, + 'no_installment' => $no_installment, + 'max_installment' => $max_installment, + 'user_name' => $user_name, + 'user_address' => $user_address, + 'user_phone' => $user_phone, + 'merchant_ok_url' => $merchant_ok_url, + 'merchant_fail_url' => $merchant_fail_url, + 'timeout_limit' => $timeout_limit, + 'currency' => $currency, + 'test_mode' => $test_mode, + ]; + + // Curl request + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://www.paytr.com/odeme/api/get-token'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_vals); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 20); + $result = @curl_exec($ch); + + if (curl_errno($ch)) { + return back()->with('error', 'PAYTR connection error. err:'.curl_error($ch)); + } + curl_close($ch); + + $result = json_decode($result, 1); + + if ($result['status'] == 'success') { + $token = $result['token']; + + // Re-render the online-payment page but with the token + // We need to fetch the page content again + // Assuming the slug is 'online-odeme' or similar, or we can just pass a dummy page object + // if we are lazy, but correct way is to fetch the page associated with the route. + // Since we are POSTing here, we don't know the Page model context unless we fetch it. + // Let's try to assume 'online-odeme' which is common. + + $page = Page::where('template', 'online-payment')->orWhere('slug', 'online-odeme')->first(); + if (! $page) { + $page = new Page(['title' => 'Online Ödeme', 'content' => '', 'excerpt' => 'Please complete your payment below.']); + } + + return view('templates.corporate.online-payment', [ + 'page' => $page, + 'paytr_token' => $token, + ]); + + } else { + return back()->with('error', 'PAYTR failed. reason:'.$result['reason']); + } + } + + public function success(Request $request) + { + $page = Page::where('template', 'online-payment')->orWhere('slug', 'online-odeme')->first(); + if ($page) { + return redirect()->route('page.show', $page->slug)->with('success', 'Your payment has been charged successfully. This will appear on your credit card statement as "PAYTR ODEME HIZMETLERI"'); + } + + return redirect()->route('home')->with('success', 'Your payment has been charged successfully.'); + } + + public function fail(Request $request) + { + $page = Page::where('template', 'online-payment')->orWhere('slug', 'online-odeme')->first(); + if ($page) { + return redirect()->route('page.show', $page->slug)->with('error', 'Your payment has not been charged successfully! Please check your credit card information.'); + } + + return redirect()->route('home')->with('error', 'Your payment has not been charged successfully! Please check your credit card information.'); + } +} diff --git a/app/Policies/PagePolicy.php b/app/Policies/PagePolicy.php index f94c7d1..1c5d14d 100644 --- a/app/Policies/PagePolicy.php +++ b/app/Policies/PagePolicy.php @@ -11,13 +11,6 @@ use Illuminate\Auth\Access\HandlesAuthorization; class PagePolicy { use HandlesAuthorization; - - public function before(AuthUser $user, $ability) - { - if ($user->hasRole('super_admin')) { - return true; - } - } public function viewAny(AuthUser $authUser): bool { diff --git a/app/Policies/ProductCategoryPolicy.php b/app/Policies/ProductCategoryPolicy.php new file mode 100644 index 0000000..2165c26 --- /dev/null +++ b/app/Policies/ProductCategoryPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:ProductCategory'); + } + + public function view(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('View:ProductCategory'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:ProductCategory'); + } + + public function update(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('Update:ProductCategory'); + } + + public function delete(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('Delete:ProductCategory'); + } + + public function restore(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('Restore:ProductCategory'); + } + + public function forceDelete(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('ForceDelete:ProductCategory'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:ProductCategory'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:ProductCategory'); + } + + public function replicate(AuthUser $authUser, ProductCategory $productCategory): bool + { + return $authUser->can('Replicate:ProductCategory'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:ProductCategory'); + } + +} \ No newline at end of file diff --git a/app/Policies/ProductPolicy.php b/app/Policies/ProductPolicy.php new file mode 100644 index 0000000..012b035 --- /dev/null +++ b/app/Policies/ProductPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:Product'); + } + + public function view(AuthUser $authUser, Product $product): bool + { + return $authUser->can('View:Product'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:Product'); + } + + public function update(AuthUser $authUser, Product $product): bool + { + return $authUser->can('Update:Product'); + } + + public function delete(AuthUser $authUser, Product $product): bool + { + return $authUser->can('Delete:Product'); + } + + public function restore(AuthUser $authUser, Product $product): bool + { + return $authUser->can('Restore:Product'); + } + + public function forceDelete(AuthUser $authUser, Product $product): bool + { + return $authUser->can('ForceDelete:Product'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:Product'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:Product'); + } + + public function replicate(AuthUser $authUser, Product $product): bool + { + return $authUser->can('Replicate:Product'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:Product'); + } + +} \ No newline at end of file diff --git a/app/Policies/SiteTranslationPolicy.php b/app/Policies/SiteTranslationPolicy.php new file mode 100644 index 0000000..e383240 --- /dev/null +++ b/app/Policies/SiteTranslationPolicy.php @@ -0,0 +1,70 @@ +can('ViewAny:SiteTranslation'); + } + + public function view(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('View:SiteTranslation'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:SiteTranslation'); + } + + public function update(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('Update:SiteTranslation'); + } + + public function delete(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('Delete:SiteTranslation'); + } + + public function restore(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('Restore:SiteTranslation'); + } + + public function forceDelete(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('ForceDelete:SiteTranslation'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:SiteTranslation'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:SiteTranslation'); + } + + public function replicate(AuthUser $authUser, SiteTranslation $siteTranslation): bool + { + return $authUser->can('Replicate:SiteTranslation'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:SiteTranslation'); + } + +} \ No newline at end of file diff --git a/database/migrations/2026_02_07_001536_create_visions_table.php b/database/migrations/2026_02_07_001536_create_visions_table.php new file mode 100644 index 0000000..5d264d8 --- /dev/null +++ b/database/migrations/2026_02_07_001536_create_visions_table.php @@ -0,0 +1,27 @@ +id(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('visions'); + } +}; diff --git a/resources/views/templates/corporate/online-payment.blade.php b/resources/views/templates/corporate/online-payment.blade.php new file mode 100644 index 0000000..2513938 --- /dev/null +++ b/resources/views/templates/corporate/online-payment.blade.php @@ -0,0 +1,206 @@ +@extends('layouts.site') + +@section('content') + +
+
+
+
+
+ + {!! t('256-Bit SSL Güvenli Ödeme') !!} +
+

{!! $page->title !!}

+

{!! $page->excerpt ?? t('Kredi kartınızla hızlı, kolay ve güvenli ödeme yapın.') !!}

+
+
+ + @if(session('success')) +
+
+
+ +
+

{!! t('Ödeme Başarılı!') !!}

+

{!! session('success') !!}

+
+
+
+
+ @endif + + @if(session('error')) +
+
+
+ +
+

{!! t('Ödeme Başarısız!') !!}

+

{!! session('error') !!}

+
+
+
+
+ @endif + + @if($errors->any()) +
+
+
+
+ +

{!! t('Lütfen Bilgileri Kontrol Ediniz') !!}

+
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+
+
+ @endif + +
+
+ @if(isset($paytr_token)) +
+ +
+
+ + + +
+
+ @else +
+ +
+ +
+ + +
+ @csrf + + +
+ +
+

+
1
+ {!! t('Kart Sahibi Bilgileri') !!} +

+ +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+
+
+
+ + + @error('surname')
{{ $message }}
@enderror +
+
+
+
+ + + @error('email')
{{ $message }}
@enderror +
+
+
+
+ + + @error('phone')
{{ $message }}
@enderror +
+
+
+
+ + + @error('address')
{{ $message }}
@enderror +
+
+
+ +
+
+ + + @error('agreement')
{{ $message }}
@enderror +
+
+
+ + +
+
+

+
2
+ {!! t('Ödeme Tutarı') !!} +

+ +
+ +
+ + +
+ @error('price')
{{ $message }}
@enderror +
+ +
+
+ + {!! t('Komisyonsuz İşlem') !!} +
+
+ + {!! t('3D Secure ile Maksimum Güvenlik') !!} +
+
+
+ +
+ + +
+ + + +
+
+
+
+
+
+
+ @endif + + @if($page->content) +
+
+ {!! $page->content !!} +
+
+ @endif + +
+
+
+
+ +@endsection diff --git a/routes/web.php b/routes/web.php index 723af2c..3efa821 100644 --- a/routes/web.php +++ b/routes/web.php @@ -100,6 +100,11 @@ Route::prefix('admin/api')->middleware(['auth', \App\Http\Middleware\SuperAdminM // Products & Services Route::get('/urun-hizmet/{slug}', [\App\Http\Controllers\ProductController::class, 'show'])->name('products.show'); +// Payment Process +Route::post('/payment/process', [\App\Http\Controllers\PaymentController::class, 'process'])->name('payment.process'); +Route::any('/payment/success', [\App\Http\Controllers\PaymentController::class, 'success'])->name('payment.success'); +Route::any('/payment/fail', [\App\Http\Controllers\PaymentController::class, 'fail'])->name('payment.fail'); + // Logo Preview Route::get('/logo-preview', function (Illuminate\Http\Request $request) { $path = $request->query('path');