--- File: D:\projects\digitalvocano\Modules\PaynowGateway\app\Http\Controllers\PaynowGatewayController.php ---
<?php

namespace Modules\PaynowGateway\Http\Controllers;

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

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

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

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

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

    /**
     * Show the form for editing the specified resource.
     */
    public function edit($id)
    {
        return view('paynowgateway::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\PaynowGateway\app\Providers\EventServiceProvider.php ---
<?php

namespace Modules\PaynowGateway\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\PaynowGateway\app\Providers\PaynowGatewayServiceProvider.php ---
<?php

namespace Modules\PaynowGateway\Providers;

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

class PaynowGatewayServiceProvider extends ServiceProvider
{
    use PathNamespace;

    protected string $name = 'PaynowGateway';

    protected string $nameLower = 'paynowgateway';

    /**
     * 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\PaynowGateway\app\Providers\RouteServiceProvider.php ---
<?php

namespace Modules\PaynowGateway\Providers;

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

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

    /**
     * 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\PaynowGateway\config\config.php ---
<?php

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


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\database\seeders\PaynowGatewayDatabaseSeeder.php ---
<?php

namespace Modules\PaynowGateway\Database\Seeders;

use Illuminate\Database\Seeder;
    use App\Models\Setting; // Assuming your Setting model is in App\Models

class PaynowGatewayDatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
            $settings = [
                [
                    'key' => 'paynowgateway_enabled',
                    'value' => '0', // Default to disabled
                    'name' => 'Enable Paynow Gateway',
                    'group' => 'Payment Gateways',
                    'type' => 'checkbox',
                    'description' => 'Enable or disable the Paynow payment gateway.'
                ],
                [
                    'key' => 'paynow_integration_id',
                    'value' => '',
                    'name' => 'Paynow Integration ID',
                    'group' => 'Payment Gateways',
                    'type' => 'text',
                    'description' => 'Your Paynow Integration ID provided by Paynow.'
                ],
                [
                    'key' => 'paynow_integration_key',
                    'value' => '',
                    'name' => 'Paynow Integration Key',
                    'group' => 'Payment Gateways',
                    'type' => 'password',
                    'description' => 'Your Paynow Integration Key provided by Paynow.'
                ],
                [
                    'key' => 'paynow_mode',
                    'value' => 'test',
                    'name' => 'Paynow Mode',
                    'group' => 'Payment Gateways',
                    'type' => 'select', // View should render options: {"test": "Test/Sandbox", "live": "Live"}
                    'description' => 'Set Paynow to Test/Sandbox or Live mode.'
                ],
                [
                    'key' => 'paynow_webhook_secret',
                    'value' => '',
                    'name' => 'Paynow Webhook Secret (for hash verification)',
                    'group' => 'Payment Gateways',
                    'type' => 'password',
                    'description' => 'The secret key used to verify incoming Paynow webhooks.'
                ],
            ];
    
            foreach ($settings as $settingData) {
                // Check if the setting already exists to prevent overriding user-configured values
                $existingSetting = Setting::where('key', $settingData['key'])->first();
                if (!$existingSetting) {
                    Setting::create([
                        'key' => $settingData['key'],
                        'value' => $settingData['value'],
                        'name' => $settingData['name'],
                        'group' => $settingData['group'],
                        'type' => $settingData['type'],
                        'description' => $settingData['description'] ?? null,
                    ]);
                }
            }
    }
}


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\Http\Controllers\Admin\PaynowConfigController.php ---
<?php

namespace Modules\PaynowGateway\Http\Controllers\Admin;

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

class PaynowConfigController extends Controller
{
    public function edit()
    {
        $settingKeys = [
            'paynow_enabled',
            'paynow_integration_id',
            'paynow_integration_key',
            'paynow_mode', // 'live' or 'test'/'sandbox' (check Paynow's terminology)
            'paynow_webhook_secret', // If Paynow uses webhook secrets
        ];

        $settings = [];
        foreach ($settingKeys as $key) {
            $settings[$key] = Setting::getValue($key);
        }

        if (is_null($settings['paynow_enabled'])) {
            $settings['paynow_enabled'] = '0';
        }
        if (is_null($settings['paynow_mode'])) {
            $settings['paynow_mode'] = 'test'; // Or Paynow's default test mode value
        }

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

    public function update(Request $request)
    {
        $request->validate([
            'paynow_enabled' => 'nullable|boolean',
            'paynow_integration_id' => 'nullable|string|max:255',
            'paynow_integration_key' => 'nullable|string|max:255',
            'paynow_mode' => 'required|in:test,live', // Adjust values as per Paynow
            'paynow_webhook_secret' => 'nullable|string|max:255',
        ]);

        try {
            Setting::setValue('paynow_enabled', $request->input('paynow_enabled', '0'), 'Enable Paynow Gateway', 'Payment Gateways', 'boolean');
            Setting::setValue('paynow_integration_id', $request->input('paynow_integration_id'), 'Paynow Integration ID', 'Payment Gateways', 'text');
            Setting::setValue('paynow_integration_key', $request->input('paynow_integration_key'), 'Paynow Integration Key', 'Payment Gateways', 'password');
            Setting::setValue('paynow_mode', $request->input('paynow_mode', 'test'), 'Paynow Mode', 'Payment Gateways', 'select');
            Setting::setValue('paynow_webhook_secret', $request->input('paynow_webhook_secret'), 'Paynow Webhook Secret', 'Payment Gateways', 'password');

            return redirect()->route('admin.paynowgateway.settings.edit')
                             ->with('success', 'Paynow settings updated successfully.');
        } catch (\Exception $e) {
            Log::error('Error updating Paynow settings: ' . $e->getMessage());
            return redirect()->route('admin.paynowgateway.settings.edit')
                             ->with('error', 'Failed to update Paynow settings. Please try again.');
        }
    }
}


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\Http\Controllers\PaynowGatewayController.php ---
<?php

namespace Modules\PaynowGateway\Http\Controllers;

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

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

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

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

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

    /**
     * Show the form for editing the specified resource.
     */
    public function edit($id)
    {
        return view('paynowgateway::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\PaynowGateway\Http\Controllers\PaynowSubscriptionController.php ---
<?php

namespace Modules\PaynowGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Subscription;
use App\Models\SubscriptionPlan;
use App\Models\User;
use App\Services\CreditService;
use App\Services\WalletService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Modules\PaynowGateway\Services\PaynowService;
use Spatie\Permission\Models\Role;

class PaynowSubscriptionController extends Controller
{
    protected PaynowService $paynowService;
    protected CreditService $creditService;
    protected WalletService $walletService;

    public function __construct(
        PaynowService $paynowService,
        CreditService $creditService,
        WalletService $walletService
    ) {
        $this->paynowService = $paynowService;
        $this->creditService = $creditService;
        $this->walletService = $walletService;
    }

    public function initializeSubscriptionPayment(Request $request, SubscriptionPlan $plan)
    {
        if (setting('paynowgateway_enabled', '0') != '1') {
            return redirect()->route('subscription.plans')->with('error', 'Paynow payments are currently disabled.');
        }

        /** @var User $user */
        $user = Auth::user();
        $subscriptionReference = 'SUB-' . Str::uuid()->toString(); // Unique reference for this payment attempt

        try {
            // Create a pending local subscription record
            $pendingSubscription = Subscription::create([
                'user_id' => $user->id,
                'subscription_plan_id' => $plan->id,
                'payment_gateway' => 'paynow_gateway',
                'gateway_transaction_id' => $subscriptionReference, // Store our unique reference
                'status' => 'pending_payment',
                'price_at_purchase' => $plan->price,
                'currency_at_purchase' => $plan->currency, // Use plan's currency
                // gateway_poll_url will be set after successful initiation
            ]);

            $paymentDetails = $this->paynowService->initiateSubscriptionPayment(
                $user,
                $plan,
                $pendingSubscription->id,
                $subscriptionReference
            );

            if ($paymentDetails && $paymentDetails['status'] === 'success' && isset($paymentDetails['redirect_url'])) {
                // Update local subscription with Paynow's poll_url and potentially their reference
                $pendingSubscription->gateway_poll_url = $paymentDetails['poll_url'];
                // $pendingSubscription->gateway_transaction_id = $paymentDetails['paynow_reference']; // Or keep $subscriptionReference
                $pendingSubscription->save();
                return redirect()->away($paymentDetails['redirect_url']);
            }

            // If initiation failed at the service level
            $pendingSubscription->update(['status' => 'failed', 'notes' => 'Paynow initiation failed.']);
            Log::error('Paynow initialize subscription payment failed at controller.', [
                'service_response' => $paymentDetails, 'user_id' => $user->id, 'plan_id' => $plan->id
            ]);
            return redirect()->route('subscription.plans')->with('error', 'Could not initiate Paynow payment. Please try again.');

        } catch (\Exception $e) {
            if (isset($pendingSubscription) && $pendingSubscription->exists) {
                $pendingSubscription->update(['status' => 'failed', 'notes' => 'Error during Paynow initiation: ' . $e->getMessage()]);
            }
            Log::error("Paynow Initialize Subscription Payment Error for user {$user->id}, plan {$plan->id}: " . $e->getMessage());
            return redirect()->route('subscription.plans')->with('error', 'An error occurred: ' . $e->getMessage());
        }
    }

    public function handleSubscriptionCallback(Request $request)
    {
        /** @var User $user */
        $user = Auth::user();
        $localSubscriptionId = $request->query('subscription_id');
        $transactionReference = $request->query('ref'); // This is our $subscriptionReference

        Log::info('Paynow Subscription Callback Received:', $request->all());

        if (!$localSubscriptionId || !$transactionReference) {
            Log::error('Paynow Subscription Callback: Missing subscription_id or ref in callback.', $request->all());
            return redirect()->route('subscription.plans')->with('error', 'Invalid callback data. Please contact support.');
        }

        $subscription = Subscription::where('id', $localSubscriptionId)
                                ->where('gateway_transaction_id', $transactionReference)
                                ->where('status', 'pending_payment')
                                ->first();

        if (!$subscription) {
            Log::warning('Paynow Subscription Callback: Local subscription not found, not pending, or reference mismatch.', [
                'local_subscription_id' => $localSubscriptionId, 'transaction_reference' => $transactionReference, 'user_id' => $user->id,
            ]);
            // Check if it was already processed
            $alreadyProcessedSub = Subscription::find($localSubscriptionId);
            if ($alreadyProcessedSub && $alreadyProcessedSub->status !== 'pending_payment') {
                 return redirect()->route('dashboard')->with('info', 'This subscription payment has already been processed.');
            }
            return redirect()->route('subscription.plans')->with('error', 'Subscription record issue. Please contact support.');
        }

        if (empty($subscription->gateway_poll_url)) {
            Log::error('Paynow Subscription Callback: Poll URL missing for subscription.', ['sub_id' => $subscription->id]);
            $subscription->update(['status' => 'failed', 'notes' => 'Callback received but Poll URL was missing.']);
            return redirect()->route('subscription.plans')->with('error', 'Cannot verify payment: Poll URL missing. Contact support.');
        }

        try {
            $verificationResult = $this->paynowService->verifyPaymentStatus($subscription->gateway_poll_url);
            Log::info('Paynow Subscription Callback - Verification Result:', $verificationResult);

            if ($verificationResult && $verificationResult['paid'] === true) {
                // Cancel other active/trialing subscriptions for the user
                $user->subscriptions()->where('id', '!=', $subscription->id)->whereIn('status', ['active', 'trialing'])->update(['status' => 'cancelled', 'ends_at' => now(), 'cancelled_at' => now()]);

                $plan = $subscription->plan;
                $subscription->status = $plan->trial_period_days > 0 ? 'trialing' : 'active';
                $subscription->starts_at = now();
                $subscription->trial_ends_at = $plan->trial_period_days > 0 ? now()->addDays($plan->trial_period_days) : null;
                $subscription->ends_at = now()->add($plan->interval, $plan->interval_count);
                $subscription->gateway_transaction_id = $verificationResult['paynow_reference'] ?? $subscription->gateway_transaction_id; // Update to Paynow's ref if available
                $subscription->notes = 'Payment successful via Paynow. Status: ' . $verificationResult['status'];
                $subscription->save();

                if (function_exists('setting') && setting('credits_system_enabled', '0') == '1' && $plan->credits_awarded_on_purchase > 0) {
                    $this->creditService->awardCredits($user, $plan->credits_awarded_on_purchase, 'award_subscription_purchase', "Credits for {$plan->name} subscription", $subscription);
                }
                if (!empty($plan->target_role) && class_exists(Role::class) && Role::where('name', $plan->target_role)->where('guard_name', 'web')->exists()) {
                    $user->syncRoles([$plan->target_role]);
                }
                return redirect()->route('dashboard')->with('success', 'Subscription successfully activated via Paynow!');
            } else {
                // Payment not confirmed as paid by Paynow
                $subscription->status = 'failed';
                $subscription->notes = 'Paynow payment verification failed or payment not completed. Status: ' . ($verificationResult['status'] ?? 'unknown') . '. Message: ' . ($verificationResult['message'] ?? '');
                $subscription->save();

                Log::error('Paynow subscription verification indicated failure or pending.', [
                    'verification_data' => $verificationResult, 'request_data' => $request->all(),
                    'subscription_id' => $subscription->id
                ]);
                return redirect()->route('subscription.plans')->with('error', $verificationResult['message'] ?? 'Paynow payment verification failed or payment not completed.');
            }

        } catch (\Exception $e) {
            Log::error("Paynow Subscription Callback Exception: " . $e->getMessage(), [
                'request_data' => $request->all(),
                'subscription_id' => $subscription->id ?? null, // Use null coalescing as $subscription might not be set if first() fails
            ]);
            if (isset($subscription) && $subscription->exists) { // Check if $subscription was successfully fetched and exists
                $subscription->status = 'failed';
                $subscription->notes = 'Error during Paynow callback processing: ' . $e->getMessage();
                $subscription->save();
            }
            return redirect()->route('subscription.plans')->with('error', 'An error occurred during payment verification.');
        }
    }

    public function handleSubscriptionCancel(Request $request)
    {
        // Optionally, find and update the pending subscription to 'cancelled' if a reference is passed back
        $transactionReference = $request->query('ref');
        if ($transactionReference) {
            $subscription = Subscription::where('gateway_transaction_id', $transactionReference)
                                        ->where('status', 'pending_payment')
                                        ->first();
            if ($subscription) {
                $subscription->update(['status' => 'cancelled', 'notes' => 'User cancelled payment process on Paynow.']);
            }
        }
        return redirect()->route('subscription.plans')->with('info', 'Paynow subscription process was cancelled.');
    }

    // --- Wallet Deposit Methods ---

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

        $minDeposit = setting('wallet_min_deposit_amount', 1);
        $validated = $request->validate(['amount' => "required|numeric|min:{$minDeposit}"]);
        $amount = (float) $validated['amount'];
        /** @var User $user */
        $user = Auth::user();
        $currency = strtoupper(setting('wallet_default_currency', 'USD'));
        $transactionReference = 'WLT-' . Str::uuid()->toString();

        try {
            $paymentDetails = $this->paynowService->initiateOneTimePayment(
                $user,
                $amount,
                $currency,
                "Wallet Deposit for user {$user->email}",
                $transactionReference
            );

            if ($paymentDetails && $paymentDetails['status'] === 'success' && isset($paymentDetails['redirect_url'])) {
                // Store necessary details in session for callback verification
                session([
                    'paynow_wallet_reference' => $transactionReference,
                    'paynow_wallet_poll_url' => $paymentDetails['poll_url'],
                    'paynow_wallet_amount' => $amount,
                    'paynow_wallet_currency' => $currency,
                ]);
                return redirect()->away($paymentDetails['redirect_url']);
            }
            Log::error('Paynow initialize wallet deposit failed at controller.', [
                'service_response' => $paymentDetails, 'user_id' => $user->id, 'amount' => $amount
            ]);
            return redirect()->route('user.wallet.deposit.form')->with('error', 'Could not initiate Paynow deposit.');
        } catch (\Exception $e) {
            Log::error("Paynow Initialize Wallet Deposit Error for user {$user->id}, amount {$amount}: " . $e->getMessage());
            return redirect()->route('user.wallet.deposit.form')->with('error', 'An error occurred: ' . $e->getMessage());
        }
    }

    public function handleWalletDepositCallback(Request $request)
    {
        /** @var User $user */
        $user = Auth::user();
        $transactionReference = $request->query('ref'); // This is our $transactionReference

        // Retrieve details from session
        $sessionReference = session('paynow_wallet_reference');
        $pollUrl = session('paynow_wallet_poll_url');
        $amount = session('paynow_wallet_amount');
        $currency = session('paynow_wallet_currency');

        Log::info('Paynow Wallet Deposit Callback Received:', $request->all());

        if (!$transactionReference || $transactionReference !== $sessionReference || !$pollUrl || !$amount) {
            Log::error('Paynow Wallet Callback: Invalid or missing session data or reference mismatch.', [
                'request_ref' => $transactionReference, 'session_ref' => $sessionReference,
                'poll_url_present' => !empty($pollUrl), 'user_id' => $user->id,
            ]);
            $this->clearWalletSessionData();
            return redirect()->route('user.wallet.deposit.form')->with('error', 'Invalid deposit session or callback. Please try again.');
        }

        try {
            $verificationResult = $this->paynowService->verifyPaymentStatus($pollUrl);
            Log::info('Paynow Wallet Deposit Callback - Verification Result:', $verificationResult);

            if ($verificationResult && $verificationResult['paid'] === true) {
                // Optional: Verify amount if Paynow returns it in verification
                $paidAmount = (float) ($verificationResult['amount'] ?? $amount); // Use session amount if not in verification
                if ($paidAmount < $amount) { // Check if paid amount is less than expected
                    Log::warning('Paynow Wallet Deposit: Amount mismatch.', [
                        'expected' => $amount, 'paid' => $paidAmount, 'user_id' => $user->id, 'ref' => $transactionReference
                    ]);
                    // Decide how to handle amount mismatch, e.g., log and proceed, or error out
                    // For now, we'll proceed with the session amount if Paynow confirms 'paid'
                }

                $this->walletService->deposit(
                    $user,
                    $amount, // Use the originally intended amount
                    $currency,
                    'paynow_gateway',
                    $verificationResult['paynow_reference'] ?? $transactionReference, // Use Paynow's ref if available
                    "Wallet deposit via Paynow. Ref: {$transactionReference}"
                );

                $this->clearWalletSessionData();
                return redirect()->route('user.wallet.index')->with('success', 'Wallet deposit successful via Paynow!');
            } else {
                Log::error('Paynow wallet deposit verification indicated failure or pending.', [
                    'verification_data' => $verificationResult, 'user_id' => $user->id, 'ref' => $transactionReference
                ]);
                $this->clearWalletSessionData();
                return redirect()->route('user.wallet.deposit.form')->with('error', $verificationResult['message'] ?? 'Paynow deposit verification failed or payment not completed.');
            }
        } catch (\Exception $e) {
            Log::error("Paynow Wallet Deposit Callback Exception: " . $e->getMessage(), [
                'request_data' => $request->all(), 'user_id' => $user->id, 'ref' => $transactionReference
            ]);
            $this->clearWalletSessionData();
            return redirect()->route('user.wallet.deposit.form')->with('error', 'An error occurred during deposit verification.');
        }
    }

    private function clearWalletSessionData()
    {
        session()->forget([
            'paynow_wallet_reference',
            'paynow_wallet_poll_url',
            'paynow_wallet_amount',
            'paynow_wallet_currency'
        ]);
    }

    public function handleWalletDepositCancel(Request $request)
    {
        $this->clearWalletSessionData();
        return redirect()->route('user.wallet.deposit.form')->with('info', 'Paynow wallet deposit was cancelled.');
    }
}


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\Http\Controllers\PaynowWebhookController.php ---
<?php

namespace Modules\PaynowGateway\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Modules\PaynowGateway\Services\PaynowService;
use App\Models\Subscription;
use App\Models\User;
use App\Services\CreditService;
use App\Services\WalletService;
// use Spatie\Permission\Models\Role; // If using roles and it's not auto-discovered

class PaynowWebhookController extends Controller
{
    protected PaynowService $paynowService;
    protected WalletService $walletService;
    protected CreditService $creditService;

    public function __construct(
        PaynowService $paynowService,
        WalletService $walletService,
        CreditService $creditService
    ) {
        $this->paynowService = $paynowService;
        $this->walletService = $walletService;
        $this->creditService = $creditService;
    }

    public function handleWebhook(Request $request)
    {
        $payload = $request->all(); // Paynow typically POSTs form data for webhooks
        Log::info('Paynow Webhook Received (raw):', $payload);

        $webhookProcessingResult = $this->paynowService->processWebhook($payload);

        if (!$webhookProcessingResult['verified']) {
            Log::warning('Paynow Webhook: Verification failed (e.g., invalid hash).', [
                'payload' => $payload, 'result' => $webhookProcessingResult
            ]);
            return response()->json(['status' => 'error', 'message' => $webhookProcessingResult['message'] ?? 'Webhook verification failed'], 400);
        }

        Log::info('Paynow Webhook Processed Data:', $webhookProcessingResult);

        $transactionReference = $webhookProcessingResult['external_reference'];
        $paynowReference = $webhookProcessingResult['paynow_reference'];
        $status = strtolower($webhookProcessingResult['status']); // Ensure lowercase for consistent comparison
        $pollUrl = $webhookProcessingResult['poll_url'];

        if (!$transactionReference) {
            Log::error('Paynow Webhook: Missing transaction reference in processed data.');
            return response()->json(['status' => 'error', 'message' => 'Missing reference'], 400);
        }

        if (Str::startsWith($transactionReference, 'SUB-')) {
            $this->handleSubscriptionWebhook($transactionReference, $paynowReference, $status, $pollUrl, $webhookProcessingResult);
        } elseif (Str::startsWith($transactionReference, 'WLT-')) {
            $this->handleWalletWebhook($transactionReference, $paynowReference, $status, $pollUrl, $webhookProcessingResult);
        } else {
            Log::info("Paynow Webhook: Unhandled transaction type for reference '{$transactionReference}'");
        }

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

    protected function handleSubscriptionWebhook($localRef, $paynowRef, $eventStatus, $pollUrl, $webhookData)
    {
        $subscription = Subscription::where('gateway_transaction_id', $localRef)
                            ->orderBy('created_at', 'desc') // In case of retries or multiple attempts with same ref
                            ->first();

        if (!$subscription) {
            Log::warning("Paynow Webhook: Subscription not found for local reference '{$localRef}' or Paynow ref '{$paynowRef}'.");
            return;
        }

        // Idempotency check: If already active and webhook says 'paid', likely already processed.
        if (($subscription->status === 'active' || $subscription->status === 'trialing') && $eventStatus === 'paid') {
            Log::info("Paynow Webhook: Subscription {$subscription->id} already active/trialing. Ignoring 'paid' event for local ref '{$localRef}'.");
            // Optionally update poll_url or paynow_reference if they differ and are useful
            if ($pollUrl && $subscription->gateway_poll_url !== $pollUrl) {
                $subscription->gateway_poll_url = $pollUrl;
            }
            if ($paynowRef && $subscription->gateway_transaction_id !== $paynowRef && $subscription->gateway_transaction_id === $localRef) {
                 // If we stored localRef initially, update to Paynow's actual transaction ID if preferred
                // $subscription->gateway_transaction_id = $paynowRef;
            }
            if ($subscription->isDirty()) {
                $subscription->save();
            }
            return;
        }

        Log::info("Paynow Webhook: Processing subscription {$subscription->id} for event '{$eventStatus}'. Local ref '{$localRef}'.");

        $originalStatus = $subscription->status;

        switch ($eventStatus) {
            case 'paid': // This is a common success status from Paynow
            case 'delivered': // Another possible success status
                if ($originalStatus !== 'active' && $originalStatus !== 'trialing') {
                    $user = $subscription->user;
                    // Cancel other active/trialing subscriptions for the user
                    $user->subscriptions()->where('id', '!=', $subscription->id)->whereIn('status', ['active', 'trialing'])
                         ->update(['status' => 'cancelled', 'ends_at' => now(), 'cancelled_at' => now()]);

                    $plan = $subscription->plan;
                    $subscription->status = $plan->trial_period_days > 0 ? 'trialing' : 'active';
                    $subscription->starts_at = $subscription->starts_at ?? now(); // Keep existing if already set (e.g. by callback)
                    $subscription->trial_ends_at = $plan->trial_period_days > 0 ? ($subscription->trial_ends_at ?? now()->addDays($plan->trial_period_days)) : null;
                    $subscription->ends_at = $subscription->ends_at ?? now()->add($plan->interval, $plan->interval_count);

                    if (function_exists('setting') && setting('credits_system_enabled', '0') == '1' && $plan->credits_awarded_on_purchase > 0) {
                        // Consider adding an idempotency check for credit awarding if this webhook might re-trigger
                        $this->creditService->awardCredits($user, $plan->credits_awarded_on_purchase, 'award_subscription_purchase_webhook', "Credits for {$plan->name} (Webhook)", $subscription);
                    }
                    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()) {
                        $user->syncRoles([$plan->target_role]);
                    }
                    Log::info("Paynow Webhook: Subscription {$subscription->id} activated/updated via webhook.");
                }
                break;

            case 'cancelled': // User cancelled on Paynow, or merchant cancelled
            case 'failed':    // Payment failed
            case 'disputed':  // Payment disputed
                if ($originalStatus !== 'cancelled' && $originalStatus !== 'failed') {
                    $subscription->status = ($eventStatus === 'cancelled' || $eventStatus === 'disputed') ? 'cancelled' : 'failed';
                    if (!$subscription->ends_at || $subscription->ends_at->isFuture()) {
                        $subscription->ends_at = now();
                    }
                    $subscription->cancelled_at = $subscription->cancelled_at ?? now();
                    Log::info("Paynow Webhook: Subscription {$subscription->id} status updated to '{$eventStatus}' via webhook.");
                }
                break;

            default:
                Log::info("Paynow Webhook: Unhandled subscription event status '{$eventStatus}' for subscription {$subscription->id}. Local ref '{$localRef}'.");
        }

        $subscription->gateway_poll_url = $pollUrl ?? $subscription->gateway_poll_url;
        // Update to Paynow's reference if it's different and we were using our local one
        // $subscription->gateway_transaction_id = $paynowRef ?? $localRef;
        $subscription->notes = ($subscription->notes ? $subscription->notes . " | " : "") . "Webhook: Status '{$eventStatus}'. PaynowRef: {$paynowRef}. LocalRef: {$localRef}.";
        if ($subscription->isDirty()) {
            $subscription->save();
        }
    }

    protected function handleWalletWebhook($localRef, $paynowRef, $eventStatus, $pollUrl, $webhookData)
    {
        Log::info("Paynow Webhook: Received wallet event '{$eventStatus}' for local reference '{$localRef}'. PaynowRef: '{$paynowRef}'.");
        // Wallet deposits are primarily handled by the synchronous callback for immediate user feedback.
        // Webhooks here serve as a reconciliation mechanism or handle cases where the user's browser
        // was closed before the callback was fully processed by your application.

        // Example: If you have a `wallet_transactions` table:
        // 1. Find the transaction by $localRef (and potentially $user_id if not globally unique).
        // 2. If its status is 'pending' and $eventStatus is 'paid' or 'delivered':
        //    - Mark your local transaction as 'completed'.
        //    - Credit the user's wallet using $this->walletService->deposit(...).
        //      Ensure this action is idempotent (doesn't credit twice if webhook is resent).
        //      You might check if a wallet credit with $paynowRef already exists.
        // 3. If its status is 'pending' and $eventStatus is 'failed'/'cancelled':
        //    - Mark your local transaction as 'failed'.

        // For now, this is a placeholder for more robust wallet webhook handling.
        // You would need a separate table to track individual wallet deposit attempts to make this robust.
        if ($eventStatus === 'paid' || $eventStatus === 'delivered') {
            // Potentially find user by email if $localRef doesn't directly link to a user or pending transaction
            // $user = User::where('email', $webhookData['raw_data']['authemail'] ?? null)->first();
            // if ($user) {
            //     $amount = (float) ($webhookData['amount'] ?? 0);
            //     $currency = setting('wallet_default_currency', 'USD'); // Assuming default
            //     // Check if this deposit was already processed via callback
            //     // This requires a way to uniquely identify the deposit attempt.
            //     Log::info("Paynow Webhook: Potential wallet deposit success for {$localRef}. Amount: {$amount}. Implement robust handling.");
            // }
        }
    }
}


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

namespace Modules\PaynowGateway\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\PaynowGateway\Providers\PaynowGatewayServiceProvider.php ---
<?php

namespace Modules\PaynowGateway\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Nwidart\Modules\Traits\PathNamespace; // If you use this trait

class PaynowGatewayServiceProvider extends ServiceProvider
{
    use PathNamespace; // If you use this trait

    protected string $name = 'PaynowGateway';
    protected string $nameLower = 'paynowgateway';

    /**
     * Boot the application events.
     */
    public function boot(): void
    {
        $this->registerConfig();
        $this->registerViews();
        $this->loadMigrationsFrom(module_path($this->name, 'database/migrations'));
        $this->loadRoutesFrom(module_path($this->name, 'routes/web.php'));
        $this->loadRoutesFrom(module_path($this->name, 'routes/admin.php'));
        // $this->loadRoutesFrom(module_path($this->name, 'routes/api.php')); // If you have API routes
    }

    /**
     * Register the service provider.
     */
    public function register(): void
    {
        // Register your PaynowService
        $this->app->singleton(\Modules\PaynowGateway\Services\PaynowService::class, function ($app) {
            return new \Modules\PaynowGateway\Services\PaynowService();
        });

        // You might register an EventServiceProvider if you have module-specific events/listeners
        // $this->app->register(EventServiceProvider::class);
    }

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

    /**
     * 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);
    }

    private function getPublishableViewPaths(): array // Helper from your AuthorizeNetGatewayServiceProvider
    {
        // This logic might need adjustment based on your main app's view configuration
        // For simplicity, assuming it works as in your other modules.
        return [resource_path('views/modules/' . $this->nameLower)];
    }
}


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

namespace Modules\PaynowGateway\Providers;

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

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

    /**
     * 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\PaynowGateway\resources\views\admin\config.blade.php ---
@extends('layouts.admin')

@section('title', 'Paynow Gateway Settings')
@section('header_title', 'Paynow 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')

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

            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                <!-- Enable Paynow -->
                <div class="md:col-span-2">
                    <x-admin.toggle-switch id="paynowgateway_enabled" name="paynowgateway_enabled" label="Enable Paynow Gateway"
                                           :checked="old('paynowgateway_enabled', $settings['paynowgateway_enabled'] ?? '0') == '1'"
                                           helpText="Toggle to activate or deactivate the Paynow payment gateway." />
                    @error('paynowgateway_enabled') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <!-- Paynow Mode -->
                <div>
                    <label for="paynow_mode" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Paynow Mode</label>
                    <select name="paynow_mode" id="paynow_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('paynow_mode', $settings['paynow_mode'] ?? 'test') == 'test' ? 'selected' : '' }}>Test/Sandbox</option>
                        <option value="live" {{ old('paynow_mode', $settings['paynow_mode'] ?? '') == 'live' ? 'selected' : '' }}>Live</option>
                    </select>
                    @error('paynow_mode') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>
                <div></div> {{-- Spacer --}}

                <!-- Paynow Integration ID -->
                <div>
                    <label for="paynow_integration_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Integration ID</label>
                    <input type="text" name="paynow_integration_id" id="paynow_integration_id" value="{{ old('paynow_integration_id', $settings['paynow_integration_id'] ?? '') }}"
                           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('paynow_integration_id') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <!-- Paynow Integration Key -->
                <div>
                    <label for="paynow_integration_key" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Integration Key</label>
                    <input type="password" name="paynow_integration_key" id="paynow_integration_key" value="{{ old('paynow_integration_key', $settings['paynow_integration_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('paynow_integration_key') <p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
                </div>

                <!-- Paynow Webhook Secret (if applicable) -->
                <div>
                    <label for="paynow_webhook_secret" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Webhook Secret (if applicable)</label>
                    <input type="password" name="paynow_webhook_secret" id="paynow_webhook_secret" value="{{ old('paynow_webhook_secret', $settings['paynow_webhook_secret'] ?? '') }}"
                           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('paynow_webhook_secret') <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>
@endsection


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\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>PaynowGateway 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-paynowgateway', 'resources/assets/sass/app.scss') }} --}}
    </head>

    <body>
        {{ $slot }}

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


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\resources\views\subscriptions\checkout.blade.php ---
{{-- This is a placeholder. Paynow might redirect to their hosted page. --}}
{{-- If Paynow uses an embedded form or SDK, you'll build that here. --}}
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Subscribe to :planName via Paynow', ['planName' => $plan->name]) }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
                <h3 class="text-lg font-medium text-gray-900">Plan: {{ $plan->name }}</h3>
                <p class="mt-1 text-sm text-gray-600">Price: {{ $plan->currency }} {{ number_format($plan->price, 2) }} / {{ $plan->interval }}</p>

                @if (session('error'))
                    <div class="mb-4 p-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
                        {{ session('error') }}
                    </div>
                @endif

                <div class="mt-6">
                    <p class="text-gray-700">You will be redirected to Paynow to complete your payment.</p>
                    {{--
                        If Paynow requires a form to be submitted to their endpoint,
                        you would build that form here with the necessary hidden fields
                        populated by $paymentDetails from your controller.

                        Example:
                        <form action="{{ $redirectUrl ?? '#' }}" method="POST">
                            @foreach($formFields ?? [] as $name => $value)
                                <input type="hidden" name="{{ $name }}" value="{{ $value }}">
                            @endforeach
                            <button type="submit" class="btn btn-primary">Proceed to Paynow</button>
                        </form>
                    --}}
                    <p class="mt-4 text-sm text-gray-500">If you are not redirected automatically, please <a href="{{ $redirectUrl ?? '#' }}" class="text-blue-600 hover:underline">click here</a>.</p>
                </div>
            </div>
        </div>
    </div>
    @if(isset($redirectUrl))
    <script>
        // Optional: Auto-redirect if JavaScript is enabled
        // window.location.href = "{{ $redirectUrl }}";
    </script>
    @endif
</x-app-layout>


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\resources\views\wallet\deposit_form.blade.php ---
{{-- This is a placeholder. Similar to subscription checkout, Paynow might redirect. --}}
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Deposit Funds with Paynow') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-lg mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
                <h3 class="text-lg font-medium text-gray-900">Deposit Amount: {{ $currency }} {{ number_format($amount, 2) }}</h3>

                @if (session('error'))
                    <div class="mb-4 p-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
                        {{ session('error') }}
                    </div>
                @endif

                <div class="mt-6">
                    <p class="text-gray-700">You will be redirected to Paynow to complete your deposit.</p>
                    <p class="mt-4 text-sm text-gray-500">If you are not redirected automatically, please <a href="{{ $redirectUrl ?? '#' }}" class="text-blue-600 hover:underline">click here</a>.</p>
                </div>
            </div>
        </div>
    </div>
    @if(isset($redirectUrl))
    <script>
        // Optional: Auto-redirect
        // window.location.href = "{{ $redirectUrl }}";
    </script>
    @endif
</x-app-layout>


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

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


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

use Illuminate\Support\Facades\Route;
use Modules\PaynowGateway\Http\Controllers\Admin\PaynowConfigController;

// Routes are automatically prefixed with 'admin/paynowgateway' and named 'admin.paynowgateway.'
// by the main app's RouteServiceProvider for modules.
Route::middleware(['web', \App\Http\Middleware\IsAdminMiddleware::class]) // Ensure IsAdminMiddleware is correct
    ->group(function () {
        Route::get('settings', [PaynowConfigController::class, 'edit'])->name('settings.edit');
        Route::put('settings', [PaynowConfigController::class, 'update'])->name('settings.update');
    });


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

use Illuminate\Support\Facades\Route;
use Modules\PaynowGateway\Http\Controllers\PaynowGatewayController;

Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
    Route::apiResource('paynowgateways', PaynowGatewayController::class)->names('paynowgateway');
});


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

use Illuminate\Support\Facades\Route;
use Modules\PaynowGateway\Http\Controllers\PaynowSubscriptionController;
use Modules\PaynowGateway\Http\Controllers\PaynowWebhookController;

// Webhook route for Paynow notifications.
// This route should typically be exempt from CSRF protection.
Route::post('paynowgateway/webhook', [PaynowWebhookController::class, 'handleWebhook'])->name('paynowgateway.webhook');

Route::middleware(['web', 'auth']) // User must be authenticated
    ->prefix('subscribe/paynow')
    ->name('subscription.paynow.') // Matches main app's SubscriptionController redirection
    ->group(function () {
        // Route for initializing payment for a subscription plan
        Route::get('checkout/{subscriptionPlan:slug}', [PaynowSubscriptionController::class, 'initializeSubscriptionPayment'])->name('checkout');
        // Route for handling the callback from Paynow after payment attempt
        Route::match(['get', 'post'], 'callback', [PaynowSubscriptionController::class, 'handleSubscriptionCallback'])->name('callback');
        // Route for handling cancellation from Paynow page (if applicable)
        Route::get('cancel', [PaynowSubscriptionController::class, 'handleSubscriptionCancel'])->name('cancel');
    });

Route::middleware(['web', 'auth'])
    ->prefix('wallet/paynow')
    ->name('wallet.paynow.') // Matches main app's WalletController redirection
    ->group(function () {
        // Route for initializing a wallet deposit
        Route::get('initialize-deposit', [PaynowSubscriptionController::class, 'initializeWalletDeposit'])->name('initializeDeposit'); // WalletController redirects here with amount
        Route::match(['get', 'post'],'deposit-callback', [PaynowSubscriptionController::class, 'handleWalletDepositCallback'])->name('depositCallback');
        Route::get('deposit-cancel', [PaynowSubscriptionController::class, 'handleWalletDepositCancel'])->name('depositCancel');
    });


--- File: D:\projects\digitalvocano\Modules\PaynowGateway\Services\PaynowService.php ---
<?php

namespace Modules\PaynowGateway\Services;

use App\Models\SubscriptionPlan;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Paynow\Payments\Paynow; // Import the SDK class
use Exception; // Import base Exception

class PaynowService
{
    protected string $integrationId;
    protected string $integrationKey;
    protected string $mode;
    protected Paynow $paynow;

    public function __construct()
    {
        if (!function_exists('setting')) {
            throw new Exception("Settings helper function not found or Paynow settings are not configured.");
        }
        $this->integrationId = setting('paynow_integration_id');
        $this->integrationKey = setting('paynow_integration_key');
        $this->mode = setting('paynow_mode', 'test');

        if (empty($this->integrationId) || empty($this->integrationKey)) {
            Log::error('PaynowService: Integration ID or Key is not configured.');
            throw new Exception('Paynow Service is not configured. Missing Integration ID or Key.');
        }

        // The resultURL is where Paynow will POST to your server (webhook)
        $resultUrl = route('paynowgateway.webhook');
        // The returnURL is where the user is redirected in their browser after payment attempt
        // This will often be overridden per transaction type (subscription vs wallet)
        $defaultReturnUrl = route('dashboard');

        $this->paynow = new Paynow(
            $this->integrationId,
            $this->integrationKey,
            $resultUrl,
            $defaultReturnUrl
        );

        // Note: Paynow SDK doesn't have an explicit "test mode" switch.
        // Test vs Live is typically determined by the Integration ID/Key used.
        // Ensure your 'test' credentials from Paynow are used when 'paynow_mode' is 'test'.
    }

    public function initiateSubscriptionPayment(User $user, SubscriptionPlan $plan, int $localSubscriptionId, string $subscriptionReference)
    {
        $amount = (float)number_format($plan->price, 2, '.', '');
        $payment = $this->paynow->createPayment($subscriptionReference, $user->email);
        $payment->add("Subscription to {$plan->name} for {$plan->interval_count} {$plan->interval}(s)", $amount);

        // Set the specific return URL for subscription callbacks
        $this->paynow->setReturnUrl(route('subscription.paynow.callback', [
            'subscription_id' => $localSubscriptionId, // Pass your local subscription ID
            'ref' => $subscriptionReference          // Pass your internal reference
        ]));

        try {
            $response = $this->paynow->send($payment);

            if ($response->success()) {
                return [
                    'status' => 'success',
                    'redirect_url' => $response->redirectUrl(),
                    'poll_url' => $response->pollUrl(), // IMPORTANT: Store this URL to check transaction status later
                    'transaction_reference' => $subscriptionReference, // Your internal reference
                    'paynow_reference' => $response->paynowReference(), // Paynow's reference for this transaction
                ];
            } else {
                Log::error("PaynowService: Failed to initiate subscription payment via SDK.", [
                    'reference' => $subscriptionReference, 'user_id' => $user->id, 'plan_id' => $plan->id,
                    'error_message' => $response->error(), 'has_error' => $response->hasError()
                ]);
                throw new Exception("Could not initiate Paynow subscription payment: " . $response->error());
            }
        } catch (Exception $e) {
            Log::error("PaynowService SDK Exception during subscription initiation: " . $e->getMessage(), [
                'reference' => $subscriptionReference, 'user_id' => $user->id,
            ]);
            throw $e; // Re-throw the exception to be caught by the controller
        }
    }

    public function initiateOneTimePayment(User $user, float $amount, string $currency, string $description, string $transactionReference)
    {
        // Paynow typically works with the merchant's default currency (ZWL or USD).
        // Ensure the $amount is in the currency expected by your Paynow account configuration.
        // The $currency parameter here is for your internal tracking if needed, but Paynow might ignore it.
        $formattedAmount = (float)number_format($amount, 2, '.', '');
        $payment = $this->paynow->createPayment($transactionReference, $user->email);
        $payment->add($description, $formattedAmount);

        // Set the specific return URL for wallet deposit callbacks
        $this->paynow->setReturnUrl(route('wallet.paynow.depositCallback', ['ref' => $transactionReference]));

        try {
            $response = $this->paynow->send($payment);

            if ($response->success()) {
                return [
                    'status' => 'success',
                    'redirect_url' => $response->redirectUrl(),
                    'poll_url' => $response->pollUrl(), // IMPORTANT: Store this
                    'transaction_reference' => $transactionReference,
                    'paynow_reference' => $response->paynowReference(),
                ];
            } else {
                Log::error("PaynowService: Failed to initiate one-time payment via SDK.", [
                    'reference' => $transactionReference, 'user_id' => $user->id, 'amount' => $formattedAmount,
                    'error_message' => $response->error(),
                ]);
                throw new Exception("Could not initiate Paynow deposit: " . $response->error());
            }
        } catch (Exception $e) {
            Log::error("PaynowService SDK Exception during one-time payment initiation: " . $e->getMessage(), [
                'reference' => $transactionReference, 'user_id' => $user->id,
            ]);
            throw $e;
        }
    }

    public function verifyPaymentStatus(string $pollUrl)
    {
        if (empty($pollUrl)) {
            Log::warning('PaynowService: verifyPaymentStatus called with empty pollUrl.');
            return ['status' => 'error', 'message' => 'Poll URL is missing for verification.', 'paid' => false];
        }

        try {
            // The Paynow SDK instance ($this->paynow) is already configured with credentials.
            $statusResponse = $this->paynow->pollTransaction($pollUrl);
            Log::info('Paynow Poll Transaction Response:', (array)$statusResponse); // Cast to array for better logging

            // Structure the response for consistency in your application
            return [
                'status' => $statusResponse->status(), // e.g., "Paid", "Cancelled", "Awaiting Delivery", etc.
                'paid' => $statusResponse->paid(),     // boolean: true if the transaction was paid
                'amount' => $statusResponse->amount(),
                'paynow_reference' => $statusResponse->paynowReference(),
                'external_reference' => $statusResponse->reference(), // This is your $transactionReference
            ];
        } catch (Exception $e) {
            Log::error("PaynowService SDK Exception during pollTransaction: " . $e->getMessage(), ['poll_url' => $pollUrl]);
            return ['status' => 'error', 'message' => 'Failed to verify payment status with Paynow: ' . $e->getMessage(), 'paid' => false];
        }
    }

    public function processWebhook(array $webhookData): array
    {
        Log::info('Paynow Webhook Data Received for Processing:', $webhookData);

        // CRITICAL: Implement Webhook Hash Verification
        // Paynow's documentation should specify how the 'hash' field in the POST data is generated.
        // It typically involves:
        // 1. Concatenating specific fields from the POST data in a PRECISE order.
        // 2. Appending your Integration Key to this string.
        // 3. Calculating the SHA512 hash of the resulting string.
        // 4. Comparing this generated hash with the 'hash' value received in the POST data.

        // Example (PSEUDOCODE - YOU MUST GET THE EXACT FIELDS AND ORDER FROM PAYNOW DOCS):
        // $stringToHash = "";
        // $fieldsInOrder = ['reference', 'paynowreference', 'amount', 'status', /* ... other fields ... */];
        // foreach ($fieldsInOrder as $field) {
        //     if (isset($webhookData[$field])) {
        //         $stringToHash .= $webhookData[$field];
        //     }
        // }
        // $stringToHash .= $this->integrationKey;
        // $generatedHash = strtoupper(hash('sha512', $stringToHash));

        // if (!isset($webhookData['hash']) || !hash_equals($generatedHash, strtoupper($webhookData['hash']))) {
        //     Log::warning('Paynow Webhook: Invalid hash.', [
        //         'received_hash' => $webhookData['hash'] ?? 'NOT_SET',
        //         'generated_hash' => $generatedHash,
        //         'data_used_for_hash' => $stringToHash // Log this carefully for debugging
        //     ]);
        //     return ['verified' => false, 'status' => 'error', 'message' => 'Invalid webhook hash.'];
        // }

        Log::warning('Paynow Webhook: Hash verification is CURRENTLY SKIPPED. This MUST BE IMPLEMENTED for security before going live.');

        $status = strtolower($webhookData['status'] ?? 'unknown'); // Normalize status to lowercase

        return [
            'verified' => true, // CHANGE TO true ONLY AFTER IMPLEMENTING AND TESTING HASH VERIFICATION
            'status' => $status,
            // Paynow status 'paid' means successful. Other statuses like 'delivered', 'awaiting_delivery' might also be relevant.
            'paid' => in_array($status, ['paid', 'delivered']),
            'paynow_reference' => $webhookData['paynowreference'] ?? null,
            'external_reference' => $webhookData['reference'] ?? null, // Your $transactionReference
            'amount' => $webhookData['amount'] ?? null,
            'poll_url' => $webhookData['pollurl'] ?? null,
            'raw_data' => $webhookData, // For further processing or logging
        ];
    }
}


