feat(payment): integrate Polar.sh payment provider with checkout flow
- Build PolarProvider from scratch with proper HTTP API integration - Add encrypted configuration loading from payment_providers table via model - Implement sandbox/live environment switching with proper credential handling - Fix product creation API structure for Polar.sh requirements - Add comprehensive error handling and logging throughout checkout flow - Fix PaymentController checkout URL handling to support Polar's checkout_url response - Add debug logging for troubleshooting checkout session creation - Support both regular and trial checkout flows for Polar payments
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\User;
|
|||||||
use App\Services\Payments\PaymentOrchestrator;
|
use App\Services\Payments\PaymentOrchestrator;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class PaymentController extends Controller
|
class PaymentController extends Controller
|
||||||
@@ -75,8 +76,23 @@ class PaymentController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('PaymentController: Creating checkout session', [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'requested_provider' => $provider,
|
||||||
|
'is_trial' => $isTrial,
|
||||||
|
'options_count' => count($options),
|
||||||
|
]);
|
||||||
|
|
||||||
$result = $this->orchestrator->createCheckoutSession($user, $plan, $provider, $options);
|
$result = $this->orchestrator->createCheckoutSession($user, $plan, $provider, $options);
|
||||||
|
|
||||||
|
Log::info('PaymentController: Returning successful checkout response', [
|
||||||
|
'result_keys' => array_keys($result),
|
||||||
|
'has_checkout_url' => isset($result['checkout_url']),
|
||||||
|
'checkout_url' => $result['checkout_url'] ?? 'missing',
|
||||||
|
'provider_subscription_id' => $result['provider_subscription_id'] ?? 'missing',
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => $result,
|
'data' => $result,
|
||||||
@@ -277,6 +293,14 @@ class PaymentController extends Controller
|
|||||||
'is_trial' => false,
|
'is_trial' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Log::info('PaymentController: enhancedCheckout result', [
|
||||||
|
'result_keys' => array_keys($result),
|
||||||
|
'has_redirect_url' => isset($result['redirect_url']),
|
||||||
|
'has_session_url' => isset($result['session_url']),
|
||||||
|
'has_checkout_url' => isset($result['checkout_url']),
|
||||||
|
'checkout_url' => $result['checkout_url'] ?? 'missing',
|
||||||
|
]);
|
||||||
|
|
||||||
// Redirect to provider's checkout page
|
// Redirect to provider's checkout page
|
||||||
if (isset($result['redirect_url'])) {
|
if (isset($result['redirect_url'])) {
|
||||||
return redirect($result['redirect_url']);
|
return redirect($result['redirect_url']);
|
||||||
@@ -287,6 +311,11 @@ class PaymentController extends Controller
|
|||||||
return redirect($result['session_url']);
|
return redirect($result['session_url']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Polar checkout URL handling
|
||||||
|
if (isset($result['checkout_url'])) {
|
||||||
|
return redirect($result['checkout_url']);
|
||||||
|
}
|
||||||
|
|
||||||
session()->flash('error', 'Unable to create checkout session. Please try again.');
|
session()->flash('error', 'Unable to create checkout session. Please try again.');
|
||||||
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
@@ -332,6 +361,14 @@ class PaymentController extends Controller
|
|||||||
'trial_requires_payment_method' => $trialConfig?->trial_requires_payment_method ?? true,
|
'trial_requires_payment_method' => $trialConfig?->trial_requires_payment_method ?? true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Log::info('PaymentController: trialCheckout result', [
|
||||||
|
'result_keys' => array_keys($result),
|
||||||
|
'has_redirect_url' => isset($result['redirect_url']),
|
||||||
|
'has_session_url' => isset($result['session_url']),
|
||||||
|
'has_checkout_url' => isset($result['checkout_url']),
|
||||||
|
'checkout_url' => $result['checkout_url'] ?? 'missing',
|
||||||
|
]);
|
||||||
|
|
||||||
// Redirect to provider's checkout page
|
// Redirect to provider's checkout page
|
||||||
if (isset($result['redirect_url'])) {
|
if (isset($result['redirect_url'])) {
|
||||||
return redirect($result['redirect_url']);
|
return redirect($result['redirect_url']);
|
||||||
@@ -342,6 +379,11 @@ class PaymentController extends Controller
|
|||||||
return redirect($result['session_url']);
|
return redirect($result['session_url']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Polar checkout URL handling
|
||||||
|
if (isset($result['checkout_url'])) {
|
||||||
|
return redirect($result['checkout_url']);
|
||||||
|
}
|
||||||
|
|
||||||
session()->flash('error', 'Unable to create trial checkout session. Please try again.');
|
session()->flash('error', 'Unable to create trial checkout session. Please try again.');
|
||||||
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ namespace App\Services\Payments;
|
|||||||
|
|
||||||
use App\Contracts\Payments\PaymentProviderContract;
|
use App\Contracts\Payments\PaymentProviderContract;
|
||||||
use App\Models\Coupon;
|
use App\Models\Coupon;
|
||||||
|
use App\Models\CouponUsage;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\Subscription;
|
use App\Models\Subscription;
|
||||||
use App\Models\SubscriptionChange;
|
use App\Models\SubscriptionChange;
|
||||||
|
use App\Models\TrialExtension;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class PaymentOrchestrator
|
class PaymentOrchestrator
|
||||||
{
|
{
|
||||||
@@ -588,16 +591,41 @@ class PaymentOrchestrator
|
|||||||
*/
|
*/
|
||||||
protected function getProviderForPlan(Plan $plan, ?string $providerName = null): PaymentProviderContract
|
protected function getProviderForPlan(Plan $plan, ?string $providerName = null): PaymentProviderContract
|
||||||
{
|
{
|
||||||
|
Log::info('PaymentOrchestrator: Getting provider for plan', [
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'requested_provider' => $providerName,
|
||||||
|
]);
|
||||||
|
|
||||||
if ($providerName) {
|
if ($providerName) {
|
||||||
$provider = $this->providerRegistry->get($providerName);
|
$provider = $this->providerRegistry->get($providerName);
|
||||||
|
Log::info('PaymentOrchestrator: Checking specific provider', [
|
||||||
|
'provider_name' => $providerName,
|
||||||
|
'provider_exists' => $provider ? true : false,
|
||||||
|
'provider_active' => $provider?->isActive(),
|
||||||
|
'provider_supported' => $provider ? $this->isProviderSupportedForPlan($provider, $plan) : false,
|
||||||
|
]);
|
||||||
|
|
||||||
if ($provider && $provider->isActive() && $this->isProviderSupportedForPlan($provider, $plan)) {
|
if ($provider && $provider->isActive() && $this->isProviderSupportedForPlan($provider, $plan)) {
|
||||||
|
Log::info('PaymentOrchestrator: Using requested provider', [
|
||||||
|
'provider' => $providerName,
|
||||||
|
]);
|
||||||
|
|
||||||
return $provider;
|
return $provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first active provider that supports this plan
|
// Find the first active provider that supports this plan
|
||||||
foreach ($this->providerRegistry->getActiveProviders() as $provider) {
|
foreach ($this->providerRegistry->getActiveProviders() as $provider) {
|
||||||
|
Log::info('PaymentOrchestrator: Checking fallback provider', [
|
||||||
|
'provider' => $provider->getName(),
|
||||||
|
'supported' => $this->isProviderSupportedForPlan($provider, $plan),
|
||||||
|
]);
|
||||||
|
|
||||||
if ($this->isProviderSupportedForPlan($provider, $plan)) {
|
if ($this->isProviderSupportedForPlan($provider, $plan)) {
|
||||||
|
Log::info('PaymentOrchestrator: Using fallback provider', [
|
||||||
|
'provider' => $provider->getName(),
|
||||||
|
]);
|
||||||
|
|
||||||
return $provider;
|
return $provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,10 +653,13 @@ class PaymentOrchestrator
|
|||||||
*/
|
*/
|
||||||
protected function isProviderSupportedForPlan(PaymentProviderContract $provider, Plan $plan): bool
|
protected function isProviderSupportedForPlan(PaymentProviderContract $provider, Plan $plan): bool
|
||||||
{
|
{
|
||||||
// Check if plan has provider-specific configuration
|
// Use the same approach as Plan::supportsProvider() - check database relationship
|
||||||
$providerConfig = $plan->details['providers'][$provider->getName()] ?? null;
|
$isSupported = $plan->planProviders()
|
||||||
|
->where('provider', $provider->getName())
|
||||||
|
->where('is_enabled', true)
|
||||||
|
->exists();
|
||||||
|
|
||||||
if (! $providerConfig || ! ($providerConfig['enabled'] ?? false)) {
|
if (! $isSupported) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Services\Payments;
|
namespace App\Services\Payments;
|
||||||
|
|
||||||
use App\Contracts\Payments\PaymentProviderContract;
|
use App\Contracts\Payments\PaymentProviderContract;
|
||||||
|
use App\Models\PaymentProvider as PaymentProviderModel;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -15,8 +16,8 @@ class ProviderRegistry
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->loadConfigurations();
|
$this->loadConfigurationsFromDatabase();
|
||||||
$this->registerDefaultProviders();
|
$this->registerProvidersFromDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,14 +94,30 @@ class ProviderRegistry
|
|||||||
*/
|
*/
|
||||||
public function updateConfiguration(string $providerName, array $config): void
|
public function updateConfiguration(string $providerName, array $config): void
|
||||||
{
|
{
|
||||||
$this->configurations[$providerName] = $config;
|
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
|
||||||
|
|
||||||
Cache::put("payment_config_{$providerName}", $config);
|
// Update local cache
|
||||||
|
$this->configurations[$providerName] = array_merge(
|
||||||
|
$this->configurations[$providerName] ?? [],
|
||||||
|
['config' => $config]
|
||||||
|
);
|
||||||
|
|
||||||
Log::info('Payment provider configuration updated', [
|
// Clear main cache
|
||||||
'provider' => $providerName,
|
Cache::forget('payment_providers_config');
|
||||||
'config_keys' => array_keys($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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,47 +254,80 @@ class ProviderRegistry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load provider configurations from cache/database
|
* Load provider configurations from database
|
||||||
*/
|
*/
|
||||||
protected function loadConfigurations(): void
|
protected function loadConfigurationsFromDatabase(): void
|
||||||
{
|
{
|
||||||
// Load from cache first
|
try {
|
||||||
$cachedConfigs = Cache::get('payment_providers_config', []);
|
// Load from cache first
|
||||||
|
$cachedConfigs = Cache::get('payment_providers_config', []);
|
||||||
|
|
||||||
if (empty($cachedConfigs)) {
|
if (empty($cachedConfigs)) {
|
||||||
// Load from database or config files
|
// Load from database
|
||||||
$this->configurations = config('payment.providers', []);
|
$providers = PaymentProviderModel::where('is_active', true)->get();
|
||||||
|
|
||||||
// Cache for 1 hour
|
$this->configurations = [];
|
||||||
Cache::put('payment_providers_config', $this->configurations, 3600);
|
foreach ($providers as $provider) {
|
||||||
} else {
|
// Configuration is already cast to array by the model
|
||||||
$this->configurations = $cachedConfigs;
|
$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 default providers
|
* Register providers from database
|
||||||
*/
|
*/
|
||||||
protected function registerDefaultProviders(): void
|
protected function registerProvidersFromDatabase(): void
|
||||||
{
|
{
|
||||||
// Auto-register providers based on configuration
|
try {
|
||||||
$enabledProviders = config('payment.enabled_providers', []);
|
$activeProviders = PaymentProviderModel::where('is_active', true)->get();
|
||||||
|
|
||||||
foreach ($enabledProviders as $providerName) {
|
foreach ($activeProviders as $providerModel) {
|
||||||
$this->registerProviderByName($providerName);
|
$this->registerProviderFromModel($providerModel);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to register payment providers from database', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register provider by name using configuration
|
* Register provider from database model
|
||||||
*/
|
*/
|
||||||
protected function registerProviderByName(string $providerName): void
|
protected function registerProviderFromModel(PaymentProviderModel $providerModel): void
|
||||||
{
|
{
|
||||||
$providerClass = config("payment.providers.{$providerName}.class");
|
// Configuration is already cast to array by the model
|
||||||
|
$configData = $providerModel->configuration ?? [];
|
||||||
|
$providerClass = $configData['class'] ?? null;
|
||||||
|
|
||||||
if (! $providerClass || ! class_exists($providerClass)) {
|
if (! $providerClass || ! class_exists($providerClass)) {
|
||||||
Log::error('Payment provider class not found', [
|
Log::error('Payment provider class not found', [
|
||||||
'provider' => $providerName,
|
'provider' => $providerModel->name,
|
||||||
'class' => $providerClass,
|
'class' => $providerClass,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -285,42 +335,120 @@ class ProviderRegistry
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$config = $this->getConfiguration($providerName);
|
// Use the full configuration data for the provider
|
||||||
|
$config = $this->getConfiguration($providerModel->name);
|
||||||
$provider = new $providerClass($config);
|
$provider = new $providerClass($config);
|
||||||
|
|
||||||
if ($provider instanceof PaymentProviderContract) {
|
if ($provider instanceof PaymentProviderContract) {
|
||||||
$this->register($providerName, $provider);
|
$this->register($providerModel->name, $provider);
|
||||||
} else {
|
} else {
|
||||||
Log::error('Payment provider does not implement contract', [
|
Log::error('Payment provider does not implement contract', [
|
||||||
'provider' => $providerName,
|
'provider' => $providerModel->name,
|
||||||
'class' => $providerClass,
|
'class' => $providerClass,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Failed to register payment provider', [
|
Log::error('Failed to register payment provider', [
|
||||||
'provider' => $providerName,
|
'provider' => $providerModel->name,
|
||||||
'error' => $e->getMessage(),
|
'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)
|
* Refresh provider (useful for configuration changes)
|
||||||
*/
|
*/
|
||||||
public function refreshProvider(string $name): bool
|
public function refreshProvider(string $name): bool
|
||||||
{
|
{
|
||||||
if (! isset($this->providers[$name])) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister current instance
|
|
||||||
unset($this->providers[$name]);
|
|
||||||
|
|
||||||
// Re-register with fresh configuration
|
|
||||||
$this->registerProviderByName($name);
|
|
||||||
|
|
||||||
return isset($this->providers[$name]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,17 +456,29 @@ class ProviderRegistry
|
|||||||
*/
|
*/
|
||||||
public function toggleProvider(string $name, bool $enabled): bool
|
public function toggleProvider(string $name, bool $enabled): bool
|
||||||
{
|
{
|
||||||
$config = $this->getConfiguration($name);
|
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(),
|
||||||
|
]);
|
||||||
|
|
||||||
if (empty($config)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config['enabled'] = $enabled;
|
|
||||||
$this->updateConfiguration($name, $config);
|
|
||||||
|
|
||||||
// Refresh the provider to apply changes
|
|
||||||
return $this->refreshProvider($name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,17 +486,36 @@ class ProviderRegistry
|
|||||||
*/
|
*/
|
||||||
public function getFallbackProvider(): ?PaymentProviderContract
|
public function getFallbackProvider(): ?PaymentProviderContract
|
||||||
{
|
{
|
||||||
$fallbackProvider = config('payment.fallback_provider');
|
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)) {
|
if ($fallbackProvider && $this->has($fallbackProvider->name)) {
|
||||||
$provider = $this->get($fallbackProvider);
|
$provider = $this->get($fallbackProvider->name);
|
||||||
|
if ($provider && $provider->isActive()) {
|
||||||
if ($provider && $provider->isActive()) {
|
return $provider;
|
||||||
return $provider;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Return first active provider as fallback
|
// Fallback to environment variable
|
||||||
return $this->getActiveProviders()->first();
|
$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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AppController;
|
use App\Http\Controllers\AppController;
|
||||||
|
|
||||||
|
// DEBUG: Test route to check PolarProvider
|
||||||
|
Route::get('/debug-polar', function () {
|
||||||
|
try {
|
||||||
|
$provider = new \App\Services\Payments\Providers\PolarProvider;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'provider_class' => get_class($provider),
|
||||||
|
'is_active' => $provider->isActive(),
|
||||||
|
'config' => $provider->getConfiguration(),
|
||||||
|
'sandbox' => $provider->config['sandbox'] ?? 'unknown',
|
||||||
|
'timestamp' => '2025-12-04-17-15-00',
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
use App\Http\Controllers\ImpersonationController;
|
use App\Http\Controllers\ImpersonationController;
|
||||||
use App\Http\Controllers\WebhookController;
|
use App\Http\Controllers\WebhookController;
|
||||||
use App\Http\Middleware\CheckPageSlug;
|
use App\Http\Middleware\CheckPageSlug;
|
||||||
|
|||||||
2
storage/framework/cache/data/.gitignore
vendored
2
storage/framework/cache/data/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
Reference in New Issue
Block a user