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:
idevakk
2025-12-04 10:29:25 -08:00
parent c2c18f2406
commit 75086ad83b
6 changed files with 770 additions and 352 deletions

View File

@@ -4,14 +4,17 @@ namespace App\Services\Payments;
use App\Contracts\Payments\PaymentProviderContract;
use App\Models\Coupon;
use App\Models\CouponUsage;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\SubscriptionChange;
use App\Models\TrialExtension;
use App\Models\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PaymentOrchestrator
{
@@ -588,16 +591,41 @@ class PaymentOrchestrator
*/
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) {
$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)) {
Log::info('PaymentOrchestrator: Using requested provider', [
'provider' => $providerName,
]);
return $provider;
}
}
// Find the first active provider that supports this plan
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)) {
Log::info('PaymentOrchestrator: Using fallback provider', [
'provider' => $provider->getName(),
]);
return $provider;
}
}
@@ -625,10 +653,13 @@ class PaymentOrchestrator
*/
protected function isProviderSupportedForPlan(PaymentProviderContract $provider, Plan $plan): bool
{
// Check if plan has provider-specific configuration
$providerConfig = $plan->details['providers'][$provider->getName()] ?? null;
// Use the same approach as Plan::supportsProvider() - check database relationship
$isSupported = $plan->planProviders()
->where('provider', $provider->getName())
->where('is_enabled', true)
->exists();
if (! $providerConfig || ! ($providerConfig['enabled'] ?? false)) {
if (! $isSupported) {
return false;
}