- 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
305 lines
7.6 KiB
PHP
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;
|
|
}
|
|
}
|