Files
zemailnator/app/Models/PaymentProvider.php
idevakk 724a2d9a1f feat(payment): implement database-driven payment provider system with encrypted configuration support
- Add PaymentProviderSeeder with initial provider data (Stripe, Lemon Squeezy, Polar, OxaPay, Crypto, Activation Key)
  - Create migration to disable JSON constraints and change configuration column from JSON to TEXT
  - Update PaymentProvider model cast from 'array' to 'encrypted:array' for secure configuration storage
2025-12-03 02:32:22 -08:00

302 lines
7.4 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PaymentProvider extends Model
{
protected $fillable = [
'name',
'display_name',
'description',
'is_active',
'configuration',
'supports_recurring',
'supports_one_time',
'supported_currencies',
'webhook_url',
'webhook_secret',
'fee_structure',
'priority',
'is_fallback',
];
protected $casts = [
'is_active' => 'boolean',
'configuration' => 'encrypted:array',
'supports_recurring' => 'boolean',
'supports_one_time' => 'boolean',
'supported_currencies' => 'array',
'fee_structure' => 'array',
'is_fallback' => 'boolean',
];
/**
* Scope to get only active providers
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope to get providers that support recurring payments
*/
public function scopeRecurring($query)
{
return $query->where('supports_recurring', true);
}
/**
* Scope to get providers that support one-time payments
*/
public function scopeOneTime($query)
{
return $query->where('supports_one_time', true);
}
/**
* Scope to get providers ordered by priority
*/
public function scopeByPriority($query)
{
return $query->orderBy('priority', 'desc');
}
/**
* Get the fallback provider
*/
public static function getFallback()
{
return static::where('is_fallback', true)->active()->first();
}
/**
* Check if provider supports a specific currency
*/
public function supportsCurrency(string $currency): bool
{
return in_array(strtoupper($currency), $this->supported_currencies);
}
/**
* Get fee for a specific amount
*/
public function calculateFee(float $amount): array
{
if (! $this->fee_structure) {
return [
'fixed_fee' => 0,
'percentage_fee' => 0,
'total_fee' => 0,
'net_amount' => $amount,
];
}
$fixedFee = $this->fee_structure['fixed_fee'] ?? 0;
$percentageFee = $this->fee_structure['percentage_fee'] ?? 0;
$percentageAmount = ($amount * $percentageFee) / 100;
$totalFee = $fixedFee + $percentageAmount;
return [
'fixed_fee' => $fixedFee,
'percentage_fee' => $percentageAmount,
'total_fee' => $totalFee,
'net_amount' => $amount - $totalFee,
];
}
/**
* Get webhook endpoint URL
*/
public function getWebhookUrl(): string
{
return $this->webhook_url ?? route('webhook.payment', $this->name);
}
/**
* Update configuration
*/
public function updateConfiguration(array $config): void
{
$this->configuration = array_merge($this->configuration ?? [], $config);
$this->save();
}
/**
* Get specific configuration value
*/
public function getConfigValue(string $key, $default = null)
{
return data_get($this->configuration, $key, $default);
}
/**
* Set specific configuration value
*/
public function setConfigValue(string $key, $value): void
{
$config = $this->configuration ?? [];
data_set($config, $key, $value);
$this->configuration = $config;
$this->save();
}
/**
* Check if provider is properly configured
*/
public function isConfigured(): bool
{
$requiredFields = $this->getConfigValue('required_fields', []);
foreach ($requiredFields as $field) {
if (empty($this->getConfigValue($field))) {
return false;
}
}
return true;
}
/**
* Get provider statistics
*/
public function getStats(): array
{
$subscriptionCount = Subscription::where('provider', $this->name)->count();
$activeSubscriptionCount = Subscription::where('provider', $this->name)
->where('unified_status', 'active')
->count();
return [
'name' => $this->name,
'display_name' => $this->display_name,
'is_active' => $this->is_active,
'is_configured' => $this->isConfigured(),
'total_subscriptions' => $subscriptionCount,
'active_subscriptions' => $activeSubscriptionCount,
'supports_recurring' => $this->supports_recurring,
'supports_one_time' => $this->supports_one_time,
'supported_currencies' => $this->supported_currencies,
'is_fallback' => $this->is_fallback,
'priority' => $this->priority,
];
}
/**
* Activate provider
*/
public function activate(): void
{
$this->is_active = true;
$this->save();
}
/**
* Deactivate provider
*/
public function deactivate(): void
{
if ($this->is_fallback) {
throw new \Exception('Cannot deactivate fallback provider');
}
$this->is_active = false;
$this->save();
}
/**
* Set as fallback provider
*/
public function setAsFallback(): void
{
// Remove fallback status from other providers
static::where('is_fallback', true)->update(['is_fallback' => false]);
$this->is_fallback = true;
$this->save();
}
/**
* Remove fallback status
*/
public function removeFallback(): void
{
$this->is_fallback = false;
$this->save();
}
/**
* Get provider class name
*/
public function getProviderClass(): string
{
return $this->getConfigValue('class', '');
}
/**
* Test provider connection
*/
public function testConnection(): array
{
try {
$class = $this->getProviderClass();
if (! class_exists($class)) {
return [
'success' => false,
'error' => "Provider class {$class} not found",
];
}
$provider = new $class($this->configuration);
if (! $provider instanceof \App\Contracts\Payments\PaymentProviderContract) {
return [
'success' => false,
'error' => 'Provider class does not implement PaymentProviderContract',
];
}
$isActive = $provider->isActive();
return [
'success' => true,
'is_active' => $isActive,
'configuration_valid' => $this->isConfigured(),
];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Get all active providers ordered by priority
*/
public static function getActiveOrdered()
{
return static::active()->byPriority()->get();
}
/**
* Get providers that support a specific plan type
*/
public static function getForPlanType(bool $recurring = false)
{
$query = static::active();
if ($recurring) {
$query->recurring();
} else {
$query->oneTime();
}
return $query->byPriority()->get();
}
}