loadConfigurationsFromDatabase(); $this->registerProvidersFromDatabase(); } /** * Register a payment provider */ public function register(string $name, PaymentProviderContract $provider): void { $this->providers[$name] = $provider; Log::info('Payment provider registered', [ 'provider' => $name, 'class' => get_class($provider), ]); } /** * Get a specific provider */ public function get(string $name): ?PaymentProviderContract { return $this->providers[$name] ?? null; } /** * Get all registered providers */ public function getAllProviders(): Collection { return collect($this->providers); } /** * Get only active providers */ public function getActiveProviders(): Collection { return collect($this->providers) ->filter(fn ($provider) => $provider->isActive()); } /** * Check if provider exists */ public function has(string $name): bool { return isset($this->providers[$name]); } /** * Unregister a provider */ public function unregister(string $name): bool { if (isset($this->providers[$name])) { unset($this->providers[$name]); Log::info('Payment provider unregistered', ['provider' => $name]); return true; } return false; } /** * Get provider configuration */ public function getConfiguration(string $providerName): array { return $this->configurations[$providerName] ?? []; } /** * Update provider configuration */ public function updateConfiguration(string $providerName, array $config): void { try { // Update database - encode as JSON string since the model expects to cast it PaymentProviderModel::where('name', $providerName) ->update(['configuration' => $config]); // Laravel will automatically cast array to JSON // Update local cache $this->configurations[$providerName] = array_merge( $this->configurations[$providerName] ?? [], ['config' => $config] ); // Clear main cache Cache::forget('payment_providers_config'); Log::info('Payment provider configuration updated', [ 'provider' => $providerName, 'config_keys' => array_keys($config), ]); } catch (\Exception $e) { Log::error('Failed to update payment provider configuration', [ 'provider' => $providerName, 'error' => $e->getMessage(), ]); } } /** * Get providers that support recurring payments */ public function getRecurringProviders(): Collection { return $this->getActiveProviders() ->filter(fn ($provider) => $provider->supportsRecurring()); } /** * Get providers that support one-time payments */ public function getOneTimeProviders(): Collection { return $this->getActiveProviders() ->filter(fn ($provider) => $provider->supportsOneTime()); } /** * Get providers that support a specific currency */ public function getProvidersForCurrency(string $currency): Collection { return $this->getActiveProviders() ->filter(function ($provider) use ($currency) { return in_array($currency, $provider->getSupportedCurrencies()); }); } /** * Get provider by webhook URL pattern */ public function getProviderByWebhookUrl(string $url): ?PaymentProviderContract { return $this->getActiveProviders() ->first(function ($provider) use ($url) { $config = $provider->getConfiguration(); $webhookUrl = $config['webhook_url'] ?? null; return $webhookUrl && str_contains($url, parse_url($webhookUrl, PHP_URL_PATH)); }); } /** * Validate provider health status */ public function validateProviders(): array { $results = []; foreach ($this->providers as $name => $provider) { try { $isActive = $provider->isActive(); $config = $provider->getConfiguration(); $results[$name] = [ 'active' => $isActive, 'configured' => ! empty($config), 'supports_recurring' => $provider->supportsRecurring(), 'supports_one_time' => $provider->supportsOneTime(), 'supported_currencies' => $provider->getSupportedCurrencies(), 'last_checked' => now()->toISOString(), ]; if (! $isActive) { Log::warning('Payment provider is inactive', ['provider' => $name]); } } catch (\Exception $e) { $results[$name] = [ 'active' => false, 'error' => $e->getMessage(), 'last_checked' => now()->toISOString(), ]; Log::error('Payment provider health check failed', [ 'provider' => $name, 'error' => $e->getMessage(), ]); } } return $results; } /** * Get provider statistics */ public function getProviderStats(): array { $stats = [ 'total_providers' => count($this->providers), 'active_providers' => 0, 'recurring_providers' => 0, 'one_time_providers' => 0, 'supported_currencies' => [], 'providers' => [], ]; foreach ($this->providers as $name => $provider) { $isActive = $provider->isActive(); if ($isActive) { $stats['active_providers']++; } if ($provider->supportsRecurring()) { $stats['recurring_providers']++; } if ($provider->supportsOneTime()) { $stats['one_time_providers']++; } $stats['supported_currencies'] = array_merge( $stats['supported_currencies'], $provider->getSupportedCurrencies() ); $stats['providers'][$name] = [ 'active' => $isActive, 'class' => get_class($provider), 'supports_recurring' => $provider->supportsRecurring(), 'supports_one_time' => $provider->supportsOneTime(), 'currencies' => $provider->getSupportedCurrencies(), ]; } $stats['supported_currencies'] = array_unique($stats['supported_currencies']); return $stats; } /** * Load provider configurations from database */ protected function loadConfigurationsFromDatabase(): void { try { // Load from cache first $cachedConfigs = Cache::get('payment_providers_config', []); if (empty($cachedConfigs)) { // Load from database $providers = PaymentProviderModel::where('is_active', true)->get(); $this->configurations = []; foreach ($providers as $provider) { // Configuration is already cast to array by the model $configData = $provider->configuration ?? []; $this->configurations[$provider->name] = [ 'enabled' => $provider->is_active, 'display_name' => $provider->display_name, 'class' => $configData['class'] ?? null, 'config' => $configData, 'supports_recurring' => $provider->supports_recurring, 'supports_one_time' => $provider->supports_one_time, 'supported_currencies' => $provider->supported_currencies ?? [], 'fee_structure' => $provider->fee_structure ?? [], 'priority' => $provider->priority, 'is_fallback' => $provider->is_fallback, ]; } // Cache for 1 hour Cache::put('payment_providers_config', $this->configurations, 3600); } else { $this->configurations = $cachedConfigs; } } catch (\Exception $e) { Log::error('Failed to load payment provider configurations from database', [ 'error' => $e->getMessage(), ]); $this->configurations = []; } } /** * Register providers from database */ protected function registerProvidersFromDatabase(): void { try { $activeProviders = PaymentProviderModel::where('is_active', true)->get(); foreach ($activeProviders as $providerModel) { $this->registerProviderFromModel($providerModel); } } catch (\Exception $e) { Log::error('Failed to register payment providers from database', [ 'error' => $e->getMessage(), ]); } } /** * Register provider from database model */ protected function registerProviderFromModel(PaymentProviderModel $providerModel): void { // Configuration is already cast to array by the model $configData = $providerModel->configuration ?? []; $providerClass = $configData['class'] ?? null; if (! $providerClass || ! class_exists($providerClass)) { Log::error('Payment provider class not found', [ 'provider' => $providerModel->name, 'class' => $providerClass, ]); return; } try { // Use the full configuration data for the provider $config = $this->getConfiguration($providerModel->name); $provider = new $providerClass($config); if ($provider instanceof PaymentProviderContract) { $this->register($providerModel->name, $provider); } else { Log::error('Payment provider does not implement contract', [ 'provider' => $providerModel->name, 'class' => $providerClass, ]); } } catch (\Exception $e) { Log::error('Failed to register payment provider', [ 'provider' => $providerModel->name, 'error' => $e->getMessage(), ]); } } /** * Get provider class name based on provider name from database */ protected function getProviderClass(string $providerName): ?string { $config = $this->getConfiguration($providerName); return $config['class'] ?? null; } /** * Get API key from database configuration */ protected function getApiKeyFromConfig(string $providerName): ?string { $config = $this->getConfiguration($providerName); $configData = $config['config'] ?? []; // Try different possible API key field names $apiKeyFields = [ 'secret_key', 'api_key', 'merchant_api_key', 'private_key', 'key', ]; foreach ($apiKeyFields as $field) { if (isset($configData[$field]) && ! empty($configData[$field])) { return $configData[$field]; } } return null; } /** * Get webhook secret from database configuration */ protected function getWebhookSecretFromConfig(string $providerName): ?string { $config = $this->getConfiguration($providerName); $configData = $config['config'] ?? []; // Try different possible webhook secret field names $webhookFields = [ 'webhook_secret', 'secret', 'signing_secret', ]; foreach ($webhookFields as $field) { if (isset($configData[$field]) && ! empty($configData[$field])) { return $configData[$field]; } } return null; } /** * Refresh provider (useful for configuration changes) */ public function refreshProvider(string $name): bool { try { // Clear cache to force reload from database Cache::forget('payment_providers_config'); // Unregister current instance unset($this->providers[$name]); // Reload configurations from database $this->loadConfigurationsFromDatabase(); // Re-register from database $providerModel = PaymentProviderModel::where('name', $name) ->where('is_active', true) ->first(); if ($providerModel) { $this->registerProviderFromModel($providerModel); } return isset($this->providers[$name]); } catch (\Exception $e) { Log::error('Failed to refresh payment provider', [ 'provider' => $name, 'error' => $e->getMessage(), ]); return false; } } /** * Enable/disable a provider */ public function toggleProvider(string $name, bool $enabled): bool { try { // Update database $updated = PaymentProviderModel::where('name', $name) ->update(['is_active' => $enabled]); if (! $updated) { return false; } // Clear cache to force reload Cache::forget('payment_providers_config'); // Refresh the provider to apply changes return $this->refreshProvider($name); } catch (\Exception $e) { Log::error('Failed to toggle payment provider', [ 'provider' => $name, 'enabled' => $enabled, 'error' => $e->getMessage(), ]); return false; } } /** * Get provider for fallback */ public function getFallbackProvider(): ?PaymentProviderContract { try { // First try to get the provider marked as fallback in database $fallbackProvider = PaymentProviderModel::where('is_fallback', true) ->where('is_active', true) ->first(); if ($fallbackProvider && $this->has($fallbackProvider->name)) { $provider = $this->get($fallbackProvider->name); if ($provider && $provider->isActive()) { return $provider; } } // Fallback to environment variable $fallbackProviderName = env('PAYMENT_FALLBACK_PROVIDER', 'stripe'); if ($this->has($fallbackProviderName)) { $provider = $this->get($fallbackProviderName); if ($provider && $provider->isActive()) { return $provider; } } // Return first active provider as final fallback return $this->getActiveProviders()->first(); } catch (\Exception $e) { Log::error('Failed to get fallback provider', [ 'error' => $e->getMessage(), ]); return $this->getActiveProviders()->first(); } } }