--- File: D:\projects\digitalvocano\Modules\PaystackGateway\app\Http\Controllers\Admin\PaystackConfigController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;

class PaystackConfigController extends Controller
{
    protected $settingKeys = [
        'paystack_enabled',
        'paystack_public_key',
        'paystack_secret_key',
        'paystack_mode', // 'test' or 'live' (Paystack typically uses test/live keys rather than a mode switch in API calls)
        // 'paystack_payment_url', // Often fixed: https://api.paystack.co
        // 'paystack_merchant_email', // Sometimes required
    ];

    public function edit()
    {
        if (!function_exists('setting')) {
            // This check is good, but ideally, the setting() helper should always be available.
            // If it's from a package, ensure it's loaded globally or provided via a service provider.
            return redirect()->route('admin.dashboard')->with('error', 'Settings helper function not found.');
        }

        $settings = [];
        foreach ($this->settingKeys as $key) {
            $settings[$key] = setting($key);
        }
        $settings['paystack_mode'] = $settings['paystack_mode'] ?? 'test'; // Default to test

        return view('paystackgateway::admin.config', compact('settings'));
    }

    public function update(Request $request)
    {
        if (!function_exists('setting')) {
            return redirect()->route('admin.dashboard')->with('error', 'Settings helper function not found.');
        }

        $validated = $request->validate([
            'paystack_enabled' => 'nullable|boolean',
            'paystack_public_key' => 'nullable|string|max:255',
            'paystack_secret_key' => 'nullable|string|max:255',
            'paystack_mode' => 'required|in:test,live',
        ]);

        try {
            foreach ($this->settingKeys as $key) {
                $valueToStore = null;
                if ($key === 'paystack_enabled') {
                    $valueToStore = $request->has('paystack_enabled') ? '1' : '0';
                } elseif ($request->filled($key)) {
                    $valueToStore = $request->input($key);
                }
                Setting::updateOrCreate(['key' => $key], ['value' => $valueToStore]);
            }

            Artisan::call('cache:clear');
            Artisan::call('config:clear');
            return redirect()->back()->with('success', 'Paystack settings updated successfully.');
        } catch (\Exception $e) {
            Log::error('Error updating Paystack settings: ' . $e->getMessage());
            return redirect()->back()->with('error', 'Failed to update Paystack settings. Please check the logs.');
        }
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\app\Http\Controllers\PaystackGatewayController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class PaystackGatewayController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return view('paystackgateway::index');
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        return view('paystackgateway::create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request) {}

    /**
     * Show the specified resource.
     */
    public function show($id)
    {
        return view('paystackgateway::show');
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit($id)
    {
        return view('paystackgateway::edit');
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, $id) {}

    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id) {}
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\app\Providers\EventServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event handler mappings for the application.
     *
     * @var array<string, array<int, string>>
     */
    protected $listen = [];

    /**
     * Indicates if events should be discovered.
     *
     * @var bool
     */
    protected static $shouldDiscoverEvents = true;

    /**
     * Configure the proper event listeners for email verification.
     */
    protected function configureEmailVerification(): void {}
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\app\Providers\PaystackGatewayServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Nwidart\Modules\Traits\PathNamespace;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class PaystackGatewayServiceProvider extends ServiceProvider
{
    use PathNamespace;

    protected string $name = 'PaystackGateway';

    protected string $nameLower = 'paystackgateway';

    /**
     * Boot the application events.
     */
    public function boot(): void
    {
        $this->registerCommands();
        $this->registerCommandSchedules();
        $this->registerTranslations();
        $this->registerConfig();
        $this->registerViews();
        $this->loadMigrationsFrom(module_path($this->name, 'database/migrations'));
    }

    /**
     * Register the service provider.
     */
    public function register(): void
    {
        $this->app->register(EventServiceProvider::class);
        $this->app->register(RouteServiceProvider::class);
    }

    /**
     * Register commands in the format of Command::class
     */
    protected function registerCommands(): void
    {
        // $this->commands([]);
    }

    /**
     * Register command Schedules.
     */
    protected function registerCommandSchedules(): void
    {
        // $this->app->booted(function () {
        //     $schedule = $this->app->make(Schedule::class);
        //     $schedule->command('inspire')->hourly();
        // });
    }

    /**
     * Register translations.
     */
    public function registerTranslations(): void
    {
        $langPath = resource_path('lang/modules/'.$this->nameLower);

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, $this->nameLower);
            $this->loadJsonTranslationsFrom($langPath);
        } else {
            $this->loadTranslationsFrom(module_path($this->name, 'lang'), $this->nameLower);
            $this->loadJsonTranslationsFrom(module_path($this->name, 'lang'));
        }
    }

    /**
     * Register config.
     */
    protected function registerConfig(): void
    {
        $configPath = module_path($this->name, config('modules.paths.generator.config.path'));

        if (is_dir($configPath)) {
            $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath));

            foreach ($iterator as $file) {
                if ($file->isFile() && $file->getExtension() === 'php') {
                    $config = str_replace($configPath.DIRECTORY_SEPARATOR, '', $file->getPathname());
                    $config_key = str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $config);
                    $segments = explode('.', $this->nameLower.'.'.$config_key);

                    // Remove duplicated adjacent segments
                    $normalized = [];
                    foreach ($segments as $segment) {
                        if (end($normalized) !== $segment) {
                            $normalized[] = $segment;
                        }
                    }

                    $key = ($config === 'config.php') ? $this->nameLower : implode('.', $normalized);

                    $this->publishes([$file->getPathname() => config_path($config)], 'config');
                    $this->merge_config_from($file->getPathname(), $key);
                }
            }
        }
    }

    /**
     * Merge config from the given path recursively.
     */
    protected function merge_config_from(string $path, string $key): void
    {
        $existing = config($key, []);
        $module_config = require $path;

        config([$key => array_replace_recursive($existing, $module_config)]);
    }

    /**
     * Register views.
     */
    public function registerViews(): void
    {
        $viewPath = resource_path('views/modules/'.$this->nameLower);
        $sourcePath = module_path($this->name, 'resources/views');

        $this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']);

        $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower);

        Blade::componentNamespace(config('modules.namespace').'\\' . $this->name . '\\View\\Components', $this->nameLower);
    }

    /**
     * Get the services provided by the provider.
     */
    public function provides(): array
    {
        return [];
    }

    private function getPublishableViewPaths(): array
    {
        $paths = [];
        foreach (config('view.paths') as $path) {
            if (is_dir($path.'/modules/'.$this->nameLower)) {
                $paths[] = $path.'/modules/'.$this->nameLower;
            }
        }

        return $paths;
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\app\Providers\RouteServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    protected string $name = 'PaystackGateway';

    /**
     * Called before routes are registered.
     *
     * Register any model bindings or pattern based filters.
     */
    public function boot(): void
    {
        parent::boot();
    }

    /**
     * Define the routes for the application.
     */
    public function map(): void
    {
        $this->mapApiRoutes();
        $this->mapWebRoutes();
    }

    /**
     * Define the "web" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     */
    protected function mapWebRoutes(): void
    {
        Route::middleware('web')->group(module_path($this->name, '/routes/web.php'));
    }

    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     */
    protected function mapApiRoutes(): void
    {
        Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php'));
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\config\config.php ---
<?php

return [
    'name' => 'PaystackGateway',
];


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\database\seeders\PaystackGatewayDatabaseSeeder.php ---
<?php

namespace Modules\PaystackGateway\Database\Seeders;

use Illuminate\Database\Seeder;

class PaystackGatewayDatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        // $this->call([]);
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Http\Controllers\Admin\PaystackConfigController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;

class PaystackConfigController extends Controller
{
    protected $settingKeys = [
        'paystack_enabled',
        'paystack_public_key',
        'paystack_secret_key',
        'paystack_mode', // 'test' or 'live' (Paystack typically uses test/live keys rather than a mode switch in API calls)
        // 'paystack_payment_url', // Often fixed: https://api.paystack.co
        // 'paystack_merchant_email', // Sometimes required
    ];

    public function edit()
    {
        if (!function_exists('setting')) {
            // This check is good, but ideally, the setting() helper should always be available.
            // If it's from a package, ensure it's loaded globally or provided via a service provider.
            return redirect()->route('admin.dashboard')->with('error', 'Settings helper function not found.');
        }

        $settings = [];
        foreach ($this->settingKeys as $key) {
            $settings[$key] = setting($key);
        }
        $settings['paystack_mode'] = $settings['paystack_mode'] ?? 'test'; // Default to test
        // Default enabled to '0' if not set
        if (is_null($settings['paystack_enabled'])) {
            $settings['paystack_enabled'] = '0';
        }

        return view('paystackgateway::admin.config', compact('settings'));
    }

    public function update(Request $request)
    {
        if (!function_exists('setting')) {
            return redirect()->route('admin.dashboard')->with('error', 'Settings helper function not found.');
        }

        $validated = $request->validate([
            'paystack_enabled' => 'nullable|boolean',
            'paystack_public_key' => 'nullable|string|max:255',
            'paystack_secret_key' => 'nullable|string|max:255',
            'paystack_mode' => 'required|in:test,live',
        ]);

        try {
            // Define settings with their names, groups, and types
            $settingsData = [
                'paystack_enabled' => [
                    'value' => $request->input('paystack_enabled', '0'),
                    'name' => 'Enable Paystack Gateway',
                    'group' => 'Payment Gateways',
                    'type' => 'boolean'
                ],
                'paystack_mode' => [
                    'value' => $request->input('paystack_mode', 'test'),
                    'name' => 'Paystack Mode',
                    'group' => 'Payment Gateways',
                    'type' => 'select'
                ],
                'paystack_public_key' => [
                    'value' => $request->input('paystack_public_key'),
                    'name' => 'Paystack Public Key',
                    'group' => 'Payment Gateways',
                    'type' => 'text'
                ],
                // Secret key is handled separately
            ];

            foreach ($settingsData as $key => $data) {
                Setting::setValue($key, $data['value'], $data['name'], $data['group'], $data['type']);
            }

            // Handle secret key: only update if a new value is provided
            if ($request->filled('paystack_secret_key')) {
                Setting::setValue('paystack_secret_key', $request->input('paystack_secret_key'), 'Paystack Secret Key', 'Payment Gateways', 'password');
            }

            Artisan::call('cache:clear');
            Artisan::call('config:clear');
            return redirect()->back()->with('success', 'Paystack settings updated successfully.');
        } catch (\Exception $e) {
            Log::error('Error updating Paystack settings: ' . $e->getMessage());
            return redirect()->back()->with('error', 'Failed to update Paystack settings. Please check the logs.');
        }
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Http\Controllers\PaystackGatewayController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class PaystackGatewayController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return view('paystackgateway::index');
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        return view('paystackgateway::create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request) {}

    /**
     * Show the specified resource.
     */
    public function show($id)
    {
        return view('paystackgateway::show');
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit($id)
    {
        return view('paystackgateway::edit');
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, $id) {}

    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id) {}
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Http\Controllers\PaystackSubscriptionController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Subscription;
use App\Models\SubscriptionPlan;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Modules\PaystackGateway\Services\PaystackService;
use App\Services\CreditService;
use App\Services\WalletService; // Added

class PaystackSubscriptionController extends Controller
{
    protected PaystackService $paystackService;
    protected CreditService $creditService;
    protected WalletService $walletService; // Added

    public function __construct(PaystackService $paystackService, CreditService $creditService, WalletService $walletService)
    {
        $this->paystackService = $paystackService;
        $this->creditService = $creditService;
        $this->walletService = $walletService; // Added
    }

    public function initializePayment(Request $request, SubscriptionPlan $plan)
    {
        // --- Test with a Direct Eloquent Query ---
        $slugFromRoute = $request->route('subscriptionPlan'); // Get the raw slug or resolved model
        if (is_object($slugFromRoute) && $slugFromRoute instanceof SubscriptionPlan) {
            // If RMB resolved it (even to an empty model), get the slug it tried to use
            $slugValueToQuery = $slugFromRoute->slug ?? $request->segment(count($request->segments()));
        } else {
            $slugValueToQuery = (string) $slugFromRoute;
        }
        $directPlan = \App\Models\SubscriptionPlan::where('slug', $slugValueToQuery)->first();
        Log::debug("Paystack Initialize: Direct Eloquent query for slug '{$slugValueToQuery}'", [
            'found_plan_id_direct_query' => $directPlan ? $directPlan->id : 'NOT FOUND BY DIRECT QUERY',
            'direct_plan_attributes' => $directPlan ? $directPlan->getAttributes() : null,
        ]);
        // --- End Test with a Direct Eloquent Query ---

        // Log the incoming plan object immediately to see what Route Model Binding resolved
        if (!$plan->exists) { // Check if the model instance is "empty" (not found by RMB)
            Log::error("Paystack Initialize: Route Model Binding failed to find a SubscriptionPlan for the provided slug.", [
                'route_param_slug_from_request' => $request->route('subscriptionPlan'),
                'plan_object_class_on_failure' => get_class($plan) // Log the class of the $plan object
            ]);
            // Optionally, redirect or abort if plan not found, as further processing will fail.
            return redirect()->route('subscription.plans')->with('error', 'The selected subscription plan could not be found.');
        }
        Log::debug("Paystack Initialize: Entered initializePayment method.", [
            'route_param_slug' => $request->route('subscriptionPlan') instanceof SubscriptionPlan ? $request->route('subscriptionPlan')->slug : $request->route('subscriptionPlan'), // Get the raw slug from route
            'plan_id_resolved' => $plan->id ?? 'PLAN ID IS NULL',
            'plan_slug_resolved' => $plan->slug ?? 'PLAN SLUG IS NULL',
            'plan_name_resolved' => $plan->name ?? 'PLAN NAME IS NULL',
            'plan_price_resolved' => $plan->price ?? 'PLAN PRICE IS NULL',
            'plan_currency_resolved' => $plan->currency ?? 'PLAN CURRENCY IS NULL',
            'is_plan_model_empty' => $plan->exists === false, // Check if it's an empty model instance
            'plan_object_attributes' => $plan ? $plan->getAttributes() : null
        ]);

        if (setting('paystack_enabled', '0') != '1') {
            return redirect()->route('subscription.plans')->with('error', 'Paystack payments are currently disabled.');
        }

        /** @var User $user */
        $user = Auth::user();

        // Check for existing active subscription (similar to PayPal)
        $activeSubscription = $user->currentSubscription();

        if ($activeSubscription) {
            if ($activeSubscription->subscription_plan_id == $plan->id && $activeSubscription->payment_gateway == 'paystackgateway') {
                 return redirect()->route('subscription.plans')->with('info', 'You are already subscribed to this plan via Paystack.');
            } elseif ($activeSubscription->subscription_plan_id != $plan->id) {
                // User has an active subscription to a DIFFERENT plan. Allow them to proceed.
                // Inform them that their current plan will be cancelled if the new subscription is successful.
                session()->flash('info_plan_switch', "You are switching from '{$activeSubscription->plan->name}' to '{$plan->name}'. Your current plan will be cancelled upon successful activation of the new plan.");
            }
        }

        try {
            // $amountInKobo = $plan->price * 100; // Paystack service will handle amount based on plan

            // Create a pending subscription record
            $pendingSubscription = null;
            $subscriptionDataToCreate = [
                'user_id' => $user->id,
                'subscription_plan_id' => $plan->id,
                'payment_gateway' => 'paystackgateway',
                'gateway_transaction_id' => 'TEMP_PAYSTACK_INIT-' . Str::random(12) . '-' . time(), // Temporary initial reference
                'status' => 'pending_payment',
                'price_at_purchase' => $plan->price,
                'currency_at_purchase' => $plan->currency,
            ];
            Log::debug("Paystack Initialize: Attempting to create Subscription with data:", $subscriptionDataToCreate);

            try {
                $pendingSubscription = Subscription::create($subscriptionDataToCreate);
            } catch (\Illuminate\Database\QueryException $qe) {
                Log::critical("Paystack Initialize: DATABASE QUERY EXCEPTION during Subscription::create()", [
                    'temp_reference_attempted' => $subscriptionDataToCreate['gateway_transaction_id'],
                    'user_id' => $user->id, 
                    'plan_id' => $plan->id,
                    'error' => $qe->getMessage(),
                    'sql' => $qe->getSql(),
                    'bindings' => $qe->getBindings(),
                    'trace' => method_exists(Str::class, 'limit') ? Str::limit($qe->getTraceAsString(), 1000) : substr($qe->getTraceAsString(), 0, 1000)
                ]);
                return redirect()->route('subscription.plans')->with('error', 'Critical error: Failed to initialize your subscription record due to a database issue. Please contact support. (Ref: INIT_DB_EX)');
            } catch (\Exception $e) {
                Log::critical("Paystack Initialize: GENERAL EXCEPTION during Subscription::create()", [
                    'temp_reference_attempted' => $subscriptionDataToCreate['gateway_transaction_id'],
                    'user_id' => $user->id, 
                    'plan_id' => $plan->id,
                    'error' => $e->getMessage(),
                    'trace' => method_exists(Str::class, 'limit') ? Str::limit($e->getTraceAsString(), 1000) : substr($e->getTraceAsString(), 0, 1000)
                ]);
                return redirect()->route('subscription.plans')->with('error', 'Critical error: Failed to initialize your subscription record. Please contact support. (Ref: INIT_GEN_EX)');
            }

            if (!$pendingSubscription || !$pendingSubscription->id) {
                Log::error("Paystack Initialize: FAILED to create pending subscription record (Subscription::create() returned null or invalid object).", [
                    'temp_reference_attempted' => $subscriptionDataToCreate['gateway_transaction_id'],
                    'user_id' => $user->id, 
                    'plan_id' => $plan->id,
                    'data_attempted' => $subscriptionDataToCreate
                ]);
                return redirect()->route('subscription.plans')->with('error', 'Failed to initialize your subscription record. Please try again or contact support. (Ref: POST_CREATE_NULL)');
            } else {
                $createdSubscriptionId = $pendingSubscription->id; // Store ID for re-fetch
                Log::info("Paystack Initialize: Successfully CREATED pending subscription record in DB.", [
                    'id' => $pendingSubscription->id, 
                    'initial_temp_reference_stored' => $pendingSubscription->gateway_transaction_id,
                    'user_id' => $pendingSubscription->user_id, 
                    'plan_id' => $pendingSubscription->subscription_plan_id, 
                    'status_stored' => $pendingSubscription->status
                ]);
            }

            // Call the service method for initializing a SUBSCRIPTION transaction,
            // passing the ID of our local pending subscription.
            $paymentDetails = $this->paystackService->initializeSubscriptionTransaction($user, $plan, $pendingSubscription->id);

            if ($paymentDetails && !empty($paymentDetails['authorization_url']) && !empty($paymentDetails['reference'])) {
                // Update the local subscription with the actual reference from Paystack
                $pendingSubscription->gateway_transaction_id = $paymentDetails['reference'];
                if ($pendingSubscription->save()) {
                    Log::info("Paystack Initialize: Successfully updated local subscription with Paystack's reference.", [
                        'local_subscription_id' => $pendingSubscription->id, 
                        'paystack_reference' => $paymentDetails['reference']
                    ]);
                } else {
                    Log::error("Paystack Initialize: FAILED to SAVE Paystack's reference to local subscription.", [
                        'local_subscription_id' => $pendingSubscription->id, 
                        'attempted_paystack_reference' => $paymentDetails['reference']
                    ]);
                    $pendingSubscription->update(['status' => 'failed']); // Mark as failed
                    return redirect()->route('subscription.plans')->with('error', 'Failed to record payment reference. Please contact support.');
                }
                return redirect()->away($paymentDetails['authorization_url']);
            }
            Log::error('Paystack initialize subscription transaction failed or missing data from service.', ['response_from_service' => $paymentDetails, 'user_id' => $user->id, 'plan_id' => $plan->id]);
            $pendingSubscription->update(['status' => 'failed']); // Mark as failed
            return redirect()->route('subscription.plans')->with('error', 'Could not initiate Paystack payment. Please try again.');

        } catch (\Exception $e) {
            Log::error("Paystack Initialize Payment Error for user {$user->id}, plan {$plan->id}: " . $e->getMessage());
            return redirect()->route('subscription.plans')->with('error', 'An error occurred while initiating payment: ' . $e->getMessage());
        }
    }

    public function handleCallback(Request $request)
    {
        $reference = $request->query('reference');

        if (!$reference) {
            return redirect()->route('subscription.plans')->with('error', 'Invalid Paystack callback. Reference missing.');
        }

        try {
            $verificationData = $this->paystackService->verifyTransaction($reference);

            if ($verificationData && isset($verificationData['status']) && $verificationData['status'] === true && isset($verificationData['data']['status']) && $verificationData['data']['status'] === 'success') {
                $paystackData = $verificationData['data'];
                $metadata = $paystackData['metadata'] ?? [];
                $localSubscriptionIdFromMeta = $metadata['subscription_id'] ?? null;
                $referenceFromQuery = $request->query('reference'); // This is the $reference used for verification

                $subscription = null;

                if ($localSubscriptionIdFromMeta) {
                    $subscription = Subscription::find($localSubscriptionIdFromMeta);
                }

                // Diagnostic: Check if ANY subscription exists with this reference, regardless of status
                if (!$subscription && $referenceFromQuery) {
                    $anySubscriptionWithReference = Subscription::where('gateway_transaction_id', $referenceFromQuery)
                                                                ->where('payment_gateway', 'paystackgateway')
                                                                ->first();
                    if ($anySubscriptionWithReference) {
                        Log::info("Paystack Callback: A subscription (ID: {$anySubscriptionWithReference->id}) was found with reference '{$referenceFromQuery}', but its status is '{$anySubscriptionWithReference->status}'. Expected 'pending_payment'.");
                    } else {
                        Log::info("Paystack Callback: NO subscription record found at all with reference '{$referenceFromQuery}'.");
                    }
                }

                // If not found by metadata ID, or if metadata ID was not present, try finding by the reference
                // This assumes the reference was stored in gateway_transaction_id during initialization
                if (!$subscription && $referenceFromQuery) {
                    Log::info("Paystack Callback: Subscription not found by metadata ID '{$localSubscriptionIdFromMeta}'. Attempting lookup by reference '{$referenceFromQuery}'.");
                    $subscription = Subscription::where('gateway_transaction_id', $referenceFromQuery)
                                                ->where('payment_gateway', 'paystackgateway') // Ensure it's a Paystack sub
                                                ->where('status', 'pending_payment') // Specifically look for pending
                                                ->first();
                }

                if ($subscription && $subscription->status === 'pending_payment') { // Ensure it's the correct pending subscription
                    // Deactivate any other existing subscriptions for this user
                    $subscription->user->subscriptions()
                        ->where('id', '!=', $subscription->id)
                        ->whereIn('status', ['active', 'trialing'])
                        ->update(['status' => 'cancelled', 'ends_at' => now(), 'cancelled_at' => now()]);

                    $subscription->updateFromPaystackData($paystackData); // Implement this method in Subscription model
                    
                    // Award credits (similar to SubscriptionController)
                    $plan = $subscription->plan;
                    if (function_exists('setting') && setting('credits_system_enabled', '0') == '1' && $plan->credits_awarded_on_purchase > 0) {
                        $this->creditService->awardCredits($subscription->user, $plan->credits_awarded_on_purchase, 'award_subscription_purchase', "Credits for {$plan->name} subscription", $subscription);
                    }
                     // Assign target role if defined
                    if (!empty($plan->target_role) && class_exists(\Spatie\Permission\Models\Role::class) && \Spatie\Permission\Models\Role::where('name', $plan->target_role)->where('guard_name', 'web')->exists()) {
                        $subscription->user->syncRoles([$plan->target_role]);
                    }

                    return redirect()->route('dashboard')->with('success', 'Subscription successfully activated via Paystack!');
                }
                Log::warning('Paystack Callback: Local subscription not found or not in a pending state.', [
                    'reference' => $referenceFromQuery,
                    'local_id_from_meta' => $localSubscriptionIdFromMeta,
                    'subscription_found_id' => $subscription ? $subscription->id : null,
                    'status_if_found' => $subscription ? $subscription->status : 'N/A'
                ]);
                return redirect()->route('subscription.plans')->with('error', 'Subscription record mismatch or already processed. Please contact support.');
            }
            Log::error('Paystack transaction verification failed or payment not successful.', ['reference' => $reference, 'response' => $verificationData]);
            return redirect()->route('subscription.plans')->with('error', $verificationData['message'] ?? 'Paystack payment verification failed.');

        } catch (\Exception $e) {
            Log::error("Paystack Callback Error for reference {$reference}: " . $e->getMessage());
            return redirect()->route('subscription.plans')->with('error', 'An error occurred during payment verification: ' . $e->getMessage());
        }
    }

    // --- Wallet Deposit Methods ---

    public function initializeWalletDeposit(Request $request)
    {
        if (setting('paystack_enabled', '0') != '1' || setting('allow_wallet_deposits', '0') != '1') {
            return redirect()->route('user.wallet.deposit.form')->with('error', 'Paystack deposits are currently disabled.');
        }

        $request->validate(['amount' => 'required|numeric|min:1']);
        $amount = (float) $request->input('amount');
        /** @var User $user */
        $user = Auth::user();

        try {
            $reference = 'DEPOSIT-' . Str::random(8) . '-' . time();
            $amountInKobo = $amount * 100; // Paystack expects amount in kobo
            $currency = setting('currency_code', 'NGN'); // Paystack often defaults to NGN

            // Optionally create a pending WalletTransaction record here if needed for tracking before redirect

            $callbackUrl = route('wallet.paystack.depositCallback');
            $metadata = [
                'user_id' => $user->id,
                'deposit_amount' => $amount,
                'currency' => $currency,
                'type' => 'wallet_deposit',
                'custom_fields' => [
                    ['display_name' => "Wallet Deposit", 'variable_name' => "transaction_type", 'value' => "Wallet Top-up"]
                ]
            ];

            $paymentData = $this->paystackService->initializeTransaction($amountInKobo, $user->email, $reference, $callbackUrl, $metadata);

            if ($paymentData && isset($paymentData['status']) && $paymentData['status'] === true && isset($paymentData['data']['authorization_url'])) {
                return redirect()->away($paymentData['data']['authorization_url']);
            }

            Log::error('Paystack initialize wallet deposit failed.', ['response' => $paymentData, 'user_id' => $user->id, 'amount' => $amount]);
            return redirect()->route('user.wallet.deposit.form')->with('error', $paymentData['message'] ?? 'Could not initiate Paystack deposit. Please try again.');

        } catch (\Exception $e) {
            Log::error("Paystack Initialize Wallet Deposit Error for user {$user->id}, amount {$amount}: " . $e->getMessage());
            return redirect()->route('user.wallet.deposit.form')->with('error', 'An error occurred while initiating deposit: ' . $e->getMessage());
        }
    }

    public function handleWalletDepositCallback(Request $request)
    {
        $reference = $request->query('reference');

        if (!$reference) {
            return redirect()->route('user.wallet.deposit.form')->with('error', 'Invalid Paystack callback for deposit. Reference missing.');
        }

        try {
            $verificationData = $this->paystackService->verifyTransaction($reference);

            if ($verificationData && isset($verificationData['status']) && $verificationData['status'] === true && isset($verificationData['data']['status']) && $verificationData['data']['status'] === 'success') {
                $paystackData = $verificationData['data'];
                $amountDeposited = $paystackData['amount'] / 100; // Amount is in Kobo
                $currency = $paystackData['currency'] ?? 'NGN';
                $userIdFromMeta = $paystackData['metadata']['user_id'] ?? null;

                if (!$userIdFromMeta) {
                    Log::error('Paystack Wallet Deposit Callback: User ID missing from metadata.', ['reference' => $reference]);
                    return redirect()->route('user.wallet.deposit.form')->with('error', 'User identification failed for deposit.');
                }

                $user = User::find($userIdFromMeta);

                if (!$user) {
                    Log::error('Paystack Wallet Deposit Callback: User not found for ID from metadata.', ['reference' => $reference, 'user_id_from_meta' => $userIdFromMeta]);
                    return redirect()->route('user.wallet.deposit.form')->with('error', 'User account not found for deposit.');
                }

                // User successfully identified from metadata
                $this->walletService->deposit($user, $amountDeposited, $currency, 'paystackgateway', $reference, "Wallet deposit via Paystack");
                return redirect()->route('user.wallet.history')->with('success', 'Successfully deposited ' . $currency . ' ' . number_format($amountDeposited, 2) . ' to your wallet.');
            }
            Log::error('Paystack wallet deposit verification failed or payment not successful.', ['reference' => $reference, 'response' => $verificationData]);
            return redirect()->route('user.wallet.deposit.form')->with('error', $verificationData['message'] ?? 'Paystack payment verification failed for deposit.');
        } catch (\Exception $e) {
            Log::error("Paystack Wallet Deposit Callback Error for reference {$reference}: " . $e->getMessage());
            return redirect()->route('user.wallet.deposit.form')->with('error', 'An error occurred during deposit verification: ' . $e->getMessage());
        }
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Http\Controllers\PaystackWebhookController.php ---
<?php

namespace Modules\PaystackGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Subscription;
use App\Models\SubscriptionPlan;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Modules\PaystackGateway\Services\PaystackService;
// Potentially add: use App\Services\CreditService;
// Potentially add: use App\Services\WalletService;

class PaystackWebhookController extends Controller
{
    protected PaystackService $paystackService;

    public function __construct(PaystackService $paystackService)
    {
        $this->paystackService = $paystackService;
    }

    public function handleWebhook(Request $request)
    {
        // 1. Verify the webhook signature
        $signature = $request->header('x-paystack-signature');
        $rawBody = $request->getContent();

        if (!$this->paystackService->secretKey || !$signature) {
            Log::warning('Paystack Webhook: Missing secret key or signature for verification.');
            return response()->json(['status' => 'error', 'message' => 'Configuration error or missing signature'], 400);
        }

        $calculatedSignature = hash_hmac('sha512', $rawBody, $this->paystackService->secretKey);

        if (!hash_equals($calculatedSignature, $signature)) {
            Log::warning('Paystack Webhook: Invalid signature.', [
                'received_signature' => $signature,
                'calculated_signature' => $calculatedSignature,
                'ip_address' => $request->ip(),
            ]);
            return response()->json(['status' => 'error', 'message' => 'Signature verification failed'], 401);
        }

        // 2. Process the event
        $event = json_decode($rawBody);

        if (!$event || !isset($event->event)) {
            Log::error('Paystack Webhook: Invalid event data received or missing event type.');
            return response()->json(['status' => 'error', 'message' => 'Invalid event data'], 400);
        }

        Log::info('Paystack Webhook Received:', ['event_type' => $event->event, 'data' => (array)($event->data ?? [])]);

        $eventType = $event->event;
        $data = $event->data ?? null;

        switch ($eventType) {
            case 'charge.success':
                // This is a crucial event for recurring payments or successful one-time charges.
                if ($data && isset($data->reference)) {
                    // If it's related to a subscription, the metadata might contain subscription_id or plan_code.
                    // You might need to fetch the local subscription using $data->reference or metadata.
                    // For now, let's assume it might be a subscription payment.
                    $this->handleSuccessfulCharge($data);
                }
                break;

            case 'subscription.create': // A new subscription is created on Paystack
            case 'subscription.enable': // A previously disabled subscription is re-enabled
                // You might want to update your local subscription status if it was, for example, 'pending' or 'disabled'.
                // $data->subscription_code, $data->customer->email, $data->plan->plan_code
                break;

            case 'subscription.disable': // Subscription is disabled (e.g., due to payment failure or manual action)
                // Update your local subscription status to 'cancelled' or 'suspended'.
                // $data->subscription_code
                break;

            // Add more Paystack event types as needed:
            // - invoice.create
            // - invoice.update
            // - invoice.payment_failed
            // - transfer.success, transfer.failed (if using Paystack transfers)

            default:
                Log::info('Paystack Webhook: Received unhandled event type: ' . $eventType);
        }

        return response()->json(['status' => 'success'], 200);
    }

    protected function handleSuccessfulCharge($data)
    {
        // Example: Find subscription by reference (if you store Paystack's transaction reference)
        // $subscription = Subscription::where('gateway_transaction_id', $data->reference)->first();
        // if ($subscription && $subscription->status !== 'active') {
        //     $subscription->status = 'active';
        //     // Potentially update ends_at based on the new payment
        //     $subscription->save();
        //     Log::info("Paystack Webhook: Subscription ID {$subscription->id} marked active via charge.success for reference {$data->reference}.");
        // }
        Log::info("Paystack Webhook: Successful charge processed for reference: {$data->reference}", (array)$data);
        // If this charge is for a subscription renewal, you'd update the subscription's `ends_at` date.
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Providers\EventServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event handler mappings for the application.
     *
     * @var array<string, array<int, string>>
     */
    protected $listen = [];

    /**
     * Indicates if events should be discovered.
     *
     * @var bool
     */
    protected static $shouldDiscoverEvents = true;

    /**
     * Configure the proper event listeners for email verification.
     */
    protected function configureEmailVerification(): void {}
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Providers\PaystackGatewayServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Support\ServiceProvider;
use Modules\PaystackGateway\Services\PaystackService; // Import the service
use Illuminate\Support\Facades\Blade;
use Nwidart\Modules\Traits\PathNamespace;

class PaystackGatewayServiceProvider extends ServiceProvider
{
    /**
     * @var string $moduleName
     */
    protected string $moduleName = 'PaystackGateway';

    /**
     * @var string $moduleNameLower
     */
    protected string $moduleNameLower = 'paystackgateway';

    use PathNamespace; // Add this trait

    public function boot(): void
    {
        $this->registerConfig();
        $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
        $this->registerViews();      // Uncomment and implement this
        $this->registerTranslations(); // Uncomment and implement if you have translations
        // Route loading is handled by the main app's RouteServiceProvider loop for modules.
        // $this->loadRoutesFrom(module_path($this->moduleName, 'Routes/web.php'));
        // $this->loadRoutesFrom(module_path($this->moduleName, 'Routes/admin.php'));
        // $this->loadRoutesFrom(module_path($this->moduleName, 'Routes/api.php'));
    }

// In Modules\PaystackGateway\Providers\PaystackGatewayServiceProvider.php
public function register(): void
{
    $this->app->register(EventServiceProvider::class); // Register the module's EventServiceProvider
    $this->app->register(RouteServiceProvider::class); // Register the module's RouteServiceProvider

    $this->app->singleton(PaystackService::class, function ($app) {
        return new PaystackService();
    });
}


    protected function registerConfig(): void
    {
        $this->publishes([
            module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower . '.php'),
        ], 'config');
        $this->mergeConfigFrom(
            module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower
        );
    }

    public function registerViews(): void
    {
        $viewPath = resource_path('views/modules/'.$this->moduleNameLower);
        $sourcePath = module_path($this->moduleName, 'resources/views');

        $this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);

        $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);

        Blade::componentNamespace(config('modules.namespace').'\\' . $this->moduleName . '\\View\\Components', $this->moduleNameLower);
    }

    public function registerTranslations(): void
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, $this->moduleNameLower);
            $this->loadJsonTranslationsFrom($langPath);
        } else {
            $this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
            $this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
        }
    }

    public function provides(): array
    {
        return [];
    }

    private function getPublishableViewPaths(): array
    {
        $paths = [];
        foreach (config('view.paths') as $path) {
            if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
                $paths[] = $path.'/modules/'.$this->moduleNameLower;
            }
        }
        return $paths;
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Providers\RouteServiceProvider.php ---
<?php

namespace Modules\PaystackGateway\Providers;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    protected string $name = 'PaystackGateway'; // Module name
    protected string $moduleNamespace = 'Modules\PaystackGateway\Http\Controllers';

    /**
     * Called before routes are registered.
     *
     * Register any model bindings or pattern based filters.
     */
    public function boot(): void
    {
        parent::boot();
    }

    /**
     * Define the routes for the application.
     */
    public function map(): void
    {
        $this->mapWebRoutes();
        // $this->mapAdminRoutes(); // Admin routes are loaded by the main app's RouteServiceProvider by convention
        $this->mapApiRoutes();
    }

    /**
     * Define the "web" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     */
    protected function mapWebRoutes(): void
    {
        // The main app's RouteServiceProvider already applies the 'web' middleware.
        // This ensures controllers in web.php are correctly namespaced.
        Route::middleware('web')
            ->namespace($this->moduleNamespace)
            ->group(module_path($this->name, 'routes/web.php'));
    }

    // protected function mapAdminRoutes(): void // This method is not needed as main RSP handles admin routes
    // {
        // The main app's RouteServiceProvider handles loading admin routes,
        // applying 'web' and 'IsAdminMiddleware', prefixing with 'admin/paystackgateway',
        // naming with 'admin.paystackgateway.', and namespacing to 'Modules\PaystackGateway\Http\Controllers\Admin'.
    // }

    /**
     * Define the "api" routes for the application.
     */
    protected function mapApiRoutes(): void
    {
        // The main app's RouteServiceProvider applies 'api' middleware,
        // 'api/paystackgateway' prefix, 'api.paystackgateway.' name prefix,
        // and the base 'Modules\PaystackGateway\Http\Controllers' namespace.
        Route::namespace($this->moduleNamespace) // Assuming API controllers are directly under Http/Controllers
            ->group(module_path($this->name, 'routes/api.php'));
    }
}


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\resources\views\admin\config.blade.php ---
@extends('layouts.admin')

@section('title', 'Paystack Gateway Settings')
@section('header_title', 'Paystack Gateway Settings')

@section('content')
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">
    <div class="p-6 lg:p-8">
        @include('admin.partials.alerts') {{-- For success/error messages --}}

        <form action="{{ route('admin.paystackgateway.settings.update') }}" method="POST">
            @csrf
            @method('PUT')

            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                <!-- Enable Paystack -->
                <div class="md:col-span-2">
                     @php
                        $id = 'paystack_enabled';
                        $name = 'paystack_enabled';
                        $label = 'Enable Paystack Gateway';
                        $value = '1';
                        $checked = old('paystack_enabled', $settings['paystack_enabled'] ?? '0') == '1';
                        $helpText = 'Toggle this to activate or deactivate the Paystack payment gateway.';
                    @endphp
                    <label for="{{ $id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $label }}</label>
                    <label class="relative inline-flex items-center cursor-pointer mt-1">
                        <input type="checkbox" id="{{ $id }}" name="{{ $name }}" class="sr-only peer" value="{{ $value }}" {{ $checked ? 'checked' : '' }}>
                        <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
                    </label>
                    @if($helpText)
                        <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ $helpText }}</p>
                    @endif
                    @error('paystack_enabled') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <!-- Paystack Mode -->
                <div>
                    <label for="paystack_mode" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Paystack Mode</label>
                    <select name="paystack_mode" id="paystack_mode" required
                            class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
                        <option value="test" {{ old('paystack_mode', $settings['paystack_mode'] ?? 'test') == 'test' ? 'selected' : '' }}>Test</option>
                        <option value="live" {{ old('paystack_mode', $settings['paystack_mode'] ?? '') == 'live' ? 'selected' : '' }}>Live</option>
                    </select>
                    @error('paystack_mode') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <div></div> {{-- Spacer for grid --}}

                <!-- Paystack Public Key -->
                <div>
                    <label for="paystack_public_key" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Paystack Public Key</label>
                    <input type="text" name="paystack_public_key" id="paystack_public_key" value="{{ old('paystack_public_key', $settings['paystack_public_key'] ?? '') }}"
                           class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md focus:ring-indigo-500 focus:border-indigo-500">
                    @error('paystack_public_key') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <!-- Paystack Secret Key -->
                <div>
                    <label for="paystack_secret_key" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Paystack Secret Key</label>
                    <input type="password" name="paystack_secret_key" id="paystack_secret_key" value="" {{-- Value removed for security --}}
                           class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md focus:ring-indigo-500 focus:border-indigo-500"
                           placeholder="Leave blank to keep current secret">
                    <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enter a new secret to update it. Leave blank to keep the existing one.</p>
                    @error('paystack_secret_key') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

            </div>

            <div class="mt-8 flex justify-end">
                <button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150">
                    Save Settings
                </button>
            </div>
        </form>
    </div>
</div>

<style>
    /* Simple toggle switch style */
    input:checked ~ .dot {
        transform: translateX(100%);
        background-color: #48bb78; /* green-500 */
    }
    input:checked ~ .block {
        background-color: #a0aec0; /* gray-400 */
    }
    /* Dark mode for toggle */
    .dark input:checked ~ .dot {
        background-color: #38a169; /* green-600 */
    }
    .dark input:checked ~ .block {
        background-color: #4a5568; /* gray-600 */
    }
</style>
@endsection


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\resources\views\components\layouts\master.blade.php ---
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title>PaystackGateway Module - {{ config('app.name', 'Laravel') }}</title>

        <meta name="description" content="{{ $description ?? '' }}">
        <meta name="keywords" content="{{ $keywords ?? '' }}">
        <meta name="author" content="{{ $author ?? '' }}">

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />

        {{-- Vite CSS --}}
        {{-- {{ module_vite('build-paystackgateway', 'resources/assets/sass/app.scss') }} --}}
    </head>

    <body>
        {{ $slot }}

        {{-- Vite JS --}}
        {{-- {{ module_vite('build-paystackgateway', 'resources/assets/js/app.js') }} --}}
    </body>


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\resources\views\index.blade.php ---
<x-paystackgateway::layouts.master>
    <h1>Hello World</h1>

    <p>Module: {!! config('paystackgateway.name') !!}</p>
</x-paystackgateway::layouts.master>


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\routes\admin.php ---
<?php

use Illuminate\Support\Facades\Route;
    use Modules\PaystackGateway\Http\Controllers\Admin\PaystackConfigController;

// The main app/Providers/RouteServiceProvider.php applies 'admin/paystackgateway/' prefix
// and 'admin.paystackgateway.' name prefix.
    Route::get('settings', [PaystackConfigController::class, 'edit'])->name('settings.edit');
    Route::put('settings', [PaystackConfigController::class, 'update'])->name('settings.update');


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\routes\api.php ---
<?php

use Illuminate\Support\Facades\Route;
use Modules\PaystackGateway\Http\Controllers\PaystackGatewayController;

Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
    Route::apiResource('paystackgateways', PaystackGatewayController::class)->names('paystackgateway');
});


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\routes\web.php ---
<?php

use Illuminate\Support\Facades\Route;
use Modules\PaystackGateway\Http\Controllers\PaystackSubscriptionController;
use Modules\PaystackGateway\Http\Controllers\PaystackWebhookController; // Add this

// Route::middleware(['auth', 'verified'])->group(function () {
//     Route::resource('paystackgateways', PaystackGatewayController::class)->names('paystackgateway');
// });

// Webhook - should generally be exempt from CSRF
Route::post('webhooks/paystack', [PaystackWebhookController::class, 'handleWebhook'])->name('webhooks.paystack.handle');

Route::middleware(['web', 'auth']) // User must be authenticated
     ->prefix('subscribe/paystack')
     ->name('subscription.paystack.')
     ->group(function () {
         // The {subscriptionPlan:slug} will use route model binding
         Route::get('initialize/{subscriptionPlan:slug}', [PaystackSubscriptionController::class, 'initializePayment'])->name('initialize');
         Route::get('callback', [PaystackSubscriptionController::class, 'handleCallback'])->name('callback'); // Paystack callback
     });

Route::middleware(['web', 'auth'])
    ->prefix('wallet/paystack')
    ->name('wallet.paystack.')
    ->group(function () {
        // The WalletController redirects to this route with amount in POST/GET
        Route::match(['get', 'post'], 'initialize-deposit', [PaystackSubscriptionController::class, 'initializeWalletDeposit'])->name('initializeDeposit');
        Route::get('deposit-callback', [PaystackSubscriptionController::class, 'handleWalletDepositCallback'])->name('depositCallback');
    });


--- File: D:\projects\digitalvocano\Modules\PaystackGateway\Services\PaystackService.php ---
<?php

namespace Modules\PaystackGateway\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use App\Models\SubscriptionPlan; // Assuming your SubscriptionPlan model
use App\Models\User;             // Assuming your User model


class PaystackService
{
    protected $publicKey;
        public $secretKey; // Make public or add a getter method
    protected $baseUrl = 'https://api.paystack.co';

    public function __construct()
    {
        if (!function_exists('setting')) {
            throw new \Exception("Settings helper function not found. Ensure it's globally available.");
        }
        $this->publicKey = setting('paystack_public_key');
        $this->secretKey = setting('paystack_secret_key');
        // Note: Paystack doesn't have a 'mode' in the API calls like PayPal SDK.
        // The live/test distinction is handled by using live or test keys.
    }

    protected function makeRequest(string $method, string $uri, array $data = [])
    {
        if (!$this->secretKey) {
            Log::error('Paystack secret key is not configured.');
            return null; // Or throw an exception
        }

        $response = Http::withToken($this->secretKey)
                        ->baseUrl($this->baseUrl)
                        ->{$method}($uri, $data);

        if (!$response->successful()) {
            Log::error("Paystack API Error: {$uri}", [
                'status' => $response->status(),
                'response' => $response->json() ?? $response->body()
            ]);
            // You might want to throw a custom exception here
            return null;
        }
        return $response->json();
    }

    public function initializeTransaction(int $amountKobo, string $email, string $reference, string $callbackUrl, array $metadata = [])
    {
        $data = [
            'amount' => $amountKobo,
            'email' => $email,
            'reference' => $reference,
            'callback_url' => $callbackUrl,
            'metadata' => json_encode($metadata) // Paystack expects metadata as a JSON string
        ];
        // You can add more parameters like 'channels', 'currency' (NGN is default) if needed
        return $this->makeRequest('post', 'transaction/initialize', $data);
    }

    public function verifyTransaction(string $reference)
    {
        return $this->makeRequest('get', "transaction/verify/{$reference}");
    }

    public function createPlan(string $name, string $interval, int $amountKobo, string $description = null)
    {
        // Paystack intervals: hourly, daily, weekly, monthly, biannually, annually
        // Your system intervals: day, week, month, year. Need mapping.
        $paystackIntervalMap = [
            'day' => 'daily',
            'week' => 'weekly',
            'month' => 'monthly',
            'year' => 'annually',
        ];
        $paystackInterval = $paystackIntervalMap[strtolower($interval)] ?? 'monthly';

        return $this->makeRequest('post', 'plan', [
            'name' => $name,
            'interval' => $paystackInterval,
            'amount' => $amountKobo, // Amount in Kobo
            'description' => $description,
        ]);
    }

    /**
     * Initialize a subscription transaction with Paystack.
     *
     * @param User $user
     * @param SubscriptionPlan $localPlan
     * @param int|null $localSubscriptionId The ID of your local pending subscription record to include in metadata.
     * @return array|null An array containing 'authorization_url' and 'reference', or null on failure.
     * @throws \Exception
     */
    public function initializeSubscriptionTransaction(User $user, SubscriptionPlan $localPlan, ?int $localSubscriptionId = null): ?array
    {
        if (!$this->secretKey) {
            Log::error('PaystackService: Secret key is not configured.');
            throw new \Exception('Paystack payment gateway is not configured properly.');
        }

        $paystackPlanCode = $localPlan->paystack_plan_code;

        if (empty($paystackPlanCode)) {
            // Plan doesn't exist on Paystack yet, create it.
            $amountKobo = (int) round($localPlan->price * 100); // Ensure price is converted to Kobo
            $planName = $localPlan->name . ' - ' . $localPlan->interval . ($localPlan->interval_count > 1 ? ' ' . $localPlan->interval_count : '');

            $paystackPlan = $this->createPlan($planName, $localPlan->interval, $amountKobo, $localPlan->description);

            if ($paystackPlan && isset($paystackPlan['status']) && $paystackPlan['status'] === true && isset($paystackPlan['data']['plan_code'])) {
                $paystackPlanCode = $paystackPlan['data']['plan_code'];
                $localPlan->paystack_plan_code = $paystackPlanCode;
                $localPlan->save(); // Save the Paystack plan code to your local plan
                Log::info("PaystackService: Created Paystack plan '{$paystackPlanCode}' for local plan ID {$localPlan->id}");
            } else {
                Log::error("PaystackService: Failed to create Paystack plan for local plan ID {$localPlan->id}", ['response' => $paystackPlan]);
                throw new \Exception('Failed to create payment plan on Paystack.');
            }
        }

        // Now initialize the transaction with the Paystack plan code
        $reference = 'SUB__' . $user->id . '__' . $localPlan->id . '__' . time() . '__' . uniqid();
        $callbackUrl = route('subscription.paystack.callback'); // Ensure this route exists in your PaystackGateway module

        $data = [
            'email' => $user->email,
            'amount' => (int) round($localPlan->price * 100), // Amount in Kobo, for the first payment if not covered by plan
            'plan' => $paystackPlanCode, // Key for subscription
            'reference' => $reference,
            'callback_url' => $callbackUrl,
            'metadata' => json_encode([
                'user_id' => $user->id,
                'local_plan_id' => $localPlan->id,
                'subscription_id' => $localSubscriptionId, // Pass your local subscription ID
                'custom_fields' => [ // Paystack's recommended structure for metadata
                    ['display_name' => "User Name", "variable_name" => "user_name", "value" => $user->name],
                    ['display_name' => "Plan Name", "variable_name" => "plan_name", "value" => $localPlan->name]
                ]
            ])
        ];

        $response = $this->makeRequest('post', 'transaction/initialize', $data);

        if ($response && isset($response['status']) && $response['status'] === true && isset($response['data']['authorization_url'], $response['data']['reference'])) {
            return [
                'authorization_url' => $response['data']['authorization_url'],
                'reference' => $response['data']['reference'] // This is the reference Paystack will use
            ];
        }
        return null;
    }
}


