Files
zemailnator/app/Models/PaymentEvent.php
idevakk 27ac13948c feat: implement comprehensive multi-provider payment processing system
- Add unified payment provider architecture with contract-based design
  - Implement 6 payment providers: Stripe, Lemon Squeezy, Polar, Oxapay, Crypto, Activation Keys
  - Create subscription management with lifecycle handling (create, cancel, pause, resume, update)
  - Add coupon system with usage tracking and trial extensions
  - Build Filament admin resources for payment providers, subscriptions, coupons, and trials
  - Implement payment orchestration service with provider registry and configuration management
  - Add comprehensive payment logging and webhook handling for all providers
  - Create customer analytics dashboard with revenue, churn, and lifetime value metrics
  - Add subscription migration service for provider switching
  - Include extensive test coverage for all payment functionality
2025-11-19 09:37:00 -08:00

305 lines
7.6 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class PaymentEvent extends Model
{
protected $fillable = [
'event_type',
'level',
'data',
'user_type',
'user_id',
'request_id',
'ip_address',
'user_agent',
'expires_at',
'provider',
'webhook_event_type',
'payload',
'success',
'stored_at',
];
protected $casts = [
'data' => 'array',
'expires_at' => 'datetime',
];
/**
* Scope to get only security events
*/
public function scopeSecurity(Builder $query): Builder
{
return $query->where('event_type', 'like', 'security_%');
}
/**
* Scope to get only compliance events
*/
public function scopeCompliance(Builder $query): Builder
{
return $query->where('event_type', 'like', 'compliance_%');
}
/**
* Scope to get only webhook events
*/
public function scopeWebhooks(Builder $query): Builder
{
return $query->where('event_type', 'like', 'webhook_%');
}
/**
* Scope to get only error events
*/
public function scopeErrors(Builder $query): Builder
{
return $query->where('level', 'error');
}
/**
* Scope to get events for a specific provider
*/
public function scopeForProvider(Builder $query, string $provider): Builder
{
return $query->whereJsonContains('data->provider', $provider);
}
/**
* Scope to get events for a specific subscription
*/
public function scopeForSubscription(Builder $query, int $subscriptionId): Builder
{
return $query->whereJsonContains('data->subscription_id', $subscriptionId);
}
/**
* Scope to get events that require review
*/
public function scopeRequiresReview(Builder $query): Builder
{
return $query->whereJsonContains('data->requires_review', true);
}
/**
* Scope to get events that haven't expired
*/
public function scopeNotExpired(Builder $query): Builder
{
return $query->where(function ($q) {
$q->whereNull('expires_at')
->orWhere('expires_at', '>', now());
});
}
/**
* Scope to get expired events (for cleanup)
*/
public function scopeExpired(Builder $query): Builder
{
return $query->where('expires_at', '<', now());
}
/**
* Get the user relationship
*/
public function user()
{
return $this->morphTo();
}
/**
* Check if event is security-related
*/
public function isSecurityEvent(): bool
{
return str_starts_with($this->event_type, 'security_');
}
/**
* Check if event is compliance-related
*/
public function isComplianceEvent(): bool
{
return str_starts_with($this->event_type, 'compliance_');
}
/**
* Check if event is webhook-related
*/
public function isWebhookEvent(): bool
{
return str_starts_with($this->event_type, 'webhook_');
}
/**
* Check if event requires review
*/
public function requiresReview(): bool
{
return ($this->data['requires_review'] ?? false) ||
$this->isSecurityEvent() ||
$this->level === 'error';
}
/**
* Get the provider from event data
*/
public function getProvider(): ?string
{
return $this->data['provider'] ?? null;
}
/**
* Get the subscription ID from event data
*/
public function getSubscriptionId(): ?int
{
return $this->data['subscription_id'] ?? null;
}
/**
* Get the action from event data
*/
public function getAction(): ?string
{
return $this->data['action'] ?? null;
}
/**
* Check if event contains sensitive data
*/
public function containsSensitiveData(): bool
{
$sensitiveKeys = ['payment_method', 'card_number', 'bank_account', 'ssn', 'full_credit_card'];
foreach ($sensitiveKeys as $key) {
if (isset($this->data[$key])) {
return true;
}
}
return false;
}
/**
* Get sanitized data for display (removes sensitive information)
*/
public function getSanitizedData(): array
{
$data = $this->data;
// Remove or mask sensitive fields
$sensitivePatterns = [
'/payment_method.*?number/i',
'/card_?number/i',
'/cvv/i',
'/cvc/i',
'/ssn/i',
'/bank_?account/i',
'/routing_?number/i',
];
foreach ($sensitivePatterns as $pattern) {
$data = array_map(function ($value) use ($pattern) {
if (is_string($value) && preg_match($pattern, $value)) {
return str_repeat('*', strlen($value) - 4).substr($value, -4);
}
return $value;
}, $data);
}
return $data;
}
/**
* Export event for compliance reporting
*/
public function toComplianceArray(): array
{
return [
'id' => $this->id,
'event_type' => $this->event_type,
'level' => $this->level,
'created_at' => $this->created_at->toISOString(),
'user_id' => $this->user_id,
'user_type' => $this->user_type,
'request_id' => $this->request_id,
'ip_address' => $this->ip_address,
'provider' => $this->getProvider(),
'subscription_id' => $this->getSubscriptionId(),
'action' => $this->getAction(),
'requires_review' => $this->requiresReview(),
'is_security_event' => $this->isSecurityEvent(),
'is_compliance_event' => $this->isComplianceEvent(),
'contains_sensitive_data' => $this->containsSensitiveData(),
];
}
/**
* Clean up old events based on retention policy
*/
public static function cleanup(): array
{
$results = [];
// Clean up expired webhook payloads
$webhookCleanup = static::webhooks()
->expired()
->delete();
$results['webhook_payloads'] = $webhookCleanup;
// Clean up old debug events
$debugCleanup = static::where('level', 'debug')
->where('created_at', '<', now()->subDays(90))
->delete();
$results['debug_events'] = $debugCleanup;
// Clean up old info events (keep for 1 year)
$infoCleanup = static::where('level', 'info')
->where('created_at', '<', now()->subYear())
->whereNot(function ($query) {
$query->security()
->compliance();
})
->delete();
$results['info_events'] = $infoCleanup;
return $results;
}
/**
* Get events by date range for reporting
*/
public static function getByDateRange(\DateTime $start, \DateTime $end, array $filters = []): Builder
{
$query = static::whereBetween('created_at', [$start, $end]);
if (! empty($filters['event_types'])) {
$query->whereIn('event_type', $filters['event_types']);
}
if (! empty($filters['levels'])) {
$query->whereIn('level', $filters['levels']);
}
if (! empty($filters['user_id'])) {
$query->where('user_id', $filters['user_id']);
}
if (! empty($filters['provider'])) {
$query->forProvider($filters['provider']);
}
return $query;
}
}