context = [ 'request_id' => uniqid('pay_', true), 'timestamp' => now()->toISOString(), 'user_agent' => $this->getUserAgent(), 'ip_address' => $this->getIpAddress(), ]; } /** * Log a payment event */ public function logEvent(string $eventType, array $data = [], ?string $level = 'info'): void { $eventData = array_merge($this->context, [ 'event_type' => $eventType, 'user_id' => $this->getUserId(), 'data' => $data, 'level' => $level, ]); // Log to Laravel logs $this->logToFile("Payment event: {$eventType}", $eventData, $level); // Store in database for audit trail $this->storeEvent($eventType, $data, $level); } /** * Log an error event */ public function logError(string $eventType, array $data = [], ?\Exception $exception = null): void { $errorData = $data; if ($exception) { $errorData['exception'] = [ 'message' => $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'trace' => $exception->getTraceAsString(), ]; } $this->logEvent($eventType, $errorData, 'error'); } /** * Log a security event */ public function logSecurityEvent(string $eventType, array $data = []): void { $securityData = array_merge($data, [ 'security_level' => 'high', 'requires_review' => true, ]); $this->logEvent("security_{$eventType}", $securityData, 'warning'); // Additional security logging $this->logToFile("Security payment event: {$eventType}", array_merge($this->context, $securityData), 'warning'); } /** * Log webhook events */ public function logWebhook(string $provider, string $eventType, array $payload, bool $success = true): void { $webhookData = [ 'provider' => $provider, 'webhook_event_type' => $eventType, 'payload_size' => strlen(json_encode($payload)), 'payload_hash' => hash('sha256', json_encode($payload)), 'success' => $success, ]; // Don't store full payload in logs for security/size reasons $this->logEvent('webhook_received', $webhookData, $success ? 'info' : 'error'); // Store full payload in database for debugging (with retention policy) $this->storeWebhookPayload($provider, $eventType, $payload, $success); } /** * Log subscription lifecycle events */ public function logSubscriptionEvent(string $action, int $subscriptionId, array $data = []): void { $subscriptionData = array_merge($data, [ 'subscription_id' => $subscriptionId, 'action' => $action, ]); $this->logEvent("subscription_{$action}", $subscriptionData); } /** * Log payment method events */ public function logPaymentMethodEvent(string $action, array $data = []): void { $this->logEvent("payment_method_{$action}", $data); } /** * Log provider events */ public function logProviderEvent(string $provider, string $action, array $data = []): void { $providerData = array_merge($data, [ 'provider' => $provider, 'provider_action' => $action, ]); $this->logEvent("provider_{$action}", $providerData); } /** * Log admin actions */ public function logAdminAction(string $action, array $data = []): void { $adminData = array_merge($data, [ 'admin_user_id' => Auth::id(), 'admin_action' => $action, 'requires_review' => in_array($action, ['refund', 'subscription_override', 'provider_config_change']), ]); $this->logEvent("admin_{$action}", $adminData); } /** * Log migration events */ public function logMigrationEvent(string $action, array $data = []): void { $migrationData = array_merge($data, [ 'migration_action' => $action, 'batch_id' => $data['batch_id'] ?? null, ]); $this->logEvent("migration_{$action}", $migrationData); } /** * Log compliance events */ public function logComplianceEvent(string $type, array $data = []): void { $complianceData = array_merge($data, [ 'compliance_type' => $type, 'retention_required' => true, 'gdpr_relevant' => in_array($type, ['data_access', 'data_deletion', 'consent_withdrawn']), ]); $this->logEvent("compliance_{$type}", $complianceData); } /** * Get audit trail for a specific user */ public function getUserAuditTrail(int $userId, array $filters = []): array { $query = PaymentEvent::where('user_id', $userId); if (! empty($filters['event_type'])) { $query->where('event_type', $filters['event_type']); } if (! empty($filters['date_from'])) { $query->where('created_at', '>=', $filters['date_from']); } if (! empty($filters['date_to'])) { $query->where('created_at', '<=', $filters['date_to']); } if (! empty($filters['level'])) { $query->where('level', $filters['level']); } return $query->orderBy('created_at', 'desc') ->limit($filters['limit'] ?? 1000) ->get() ->toArray(); } /** * Get audit trail for a subscription */ public function getSubscriptionAuditTrail(int $subscriptionId): array { return PaymentEvent::whereJsonContains('data->subscription_id', $subscriptionId) ->orderBy('created_at', 'desc') ->get() ->toArray(); } /** * Get provider audit trail */ public function getProviderAuditTrail(string $provider, array $filters = []): array { $query = PaymentEvent::whereJsonContains('data->provider', $provider); if (! empty($filters['date_from'])) { $query->where('created_at', '>=', $filters['date_from']); } if (! empty($filters['date_to'])) { $query->where('created_at', '<=', $filters['date_to']); } return $query->orderBy('created_at', 'desc') ->limit($filters['limit'] ?? 1000) ->get() ->toArray(); } /** * Generate compliance report */ public function generateComplianceReport(array $criteria = []): array { $query = PaymentEvent::query(); if (! empty($criteria['date_from'])) { $query->where('created_at', '>=', $criteria['date_from']); } if (! empty($criteria['date_to'])) { $query->where('created_at', '<=', $criteria['date_to']); } if (! empty($criteria['event_types'])) { $query->whereIn('event_type', $criteria['event_types']); } $events = $query->get(); return [ 'report_generated_at' => now()->toISOString(), 'criteria' => $criteria, 'total_events' => $events->count(), 'events_by_type' => $events->groupBy('event_type')->map->count(), 'events_by_level' => $events->groupBy('level')->map->count(), 'security_events' => $events->filter(fn ($e) => str_contains($e->event_type, 'security'))->count(), 'compliance_events' => $events->filter(fn ($e) => str_contains($e->event_type, 'compliance'))->count(), 'retention_summary' => $this->getRetentionSummary($events), ]; } /** * Store event in database */ protected function storeEvent(string $eventType, array $data, string $level): void { try { PaymentEvent::create([ 'event_type' => $eventType, 'user_id' => Auth::id(), 'level' => $level, 'data' => array_merge($this->context, $data), 'ip_address' => $this->context['ip_address'], 'user_agent' => $this->context['user_agent'], 'request_id' => $this->context['request_id'], ]); } catch (\Exception $e) { // Fallback to file logging if database fails $this->logToFile('Failed to store payment event in database', [ 'event_type' => $eventType, 'error' => $e->getMessage(), 'data' => $data, ], 'error'); } } /** * Store webhook payload */ protected function storeWebhookPayload(string $provider, string $eventType, array $payload, bool $success): void { try { PaymentEvent::create([ 'event_type' => 'webhook_payload', 'level' => $success ? 'info' : 'error', 'data' => [ 'provider' => $provider, 'webhook_event_type' => $eventType, 'payload' => $payload, 'success' => $success, 'stored_at' => now()->toISOString(), ] + $this->context, 'ip_address' => $this->context['ip_address'], 'user_agent' => $this->context['user_agent'], 'request_id' => $this->context['request_id'], 'expires_at' => now()->addDays(30), // Webhook payloads expire after 30 days ]); } catch (\Exception $e) { $this->logToFile('Failed to store webhook payload', [ 'provider' => $provider, 'event_type' => $eventType, 'error' => $e->getMessage(), ], 'error'); } } /** * Get retention summary for compliance */ protected function getRetentionSummary($events): array { $now = now(); $retentionPeriods = [ '30_days' => $now->copy()->subDays(30), '90_days' => $now->copy()->subDays(90), '1_year' => $now->copy()->subYear(), '7_years' => $now->copy()->subYears(7), ]; return array_map(static function ($date) use ($events) { return $events->where('created_at', '>=', $date)->count(); }, $retentionPeriods); } /** * Clean up old events based on retention policy */ public function cleanupOldEvents(): array { $cleanupResults = []; // Clean up webhook payloads after 30 days $webhookCleanup = PaymentEvent::where('event_type', 'webhook_payload') ->where('expires_at', '<', now()) ->delete(); $cleanupResults['webhook_payloads'] = $webhookCleanup; // Clean up debug events after 90 days $debugCleanup = PaymentEvent::where('level', 'debug') ->where('created_at', '<', now()->subDays(90)) ->delete(); $cleanupResults['debug_events'] = $debugCleanup; // Keep compliance and security events for 7 years // This is handled by database retention policies $this->logToFile('Payment event cleanup completed', $cleanupResults, 'info'); return $cleanupResults; } /** * Set additional context for logging */ public function setContext(array $context): void { $this->context = array_merge($this->context, $context); } /** * Get current context */ public function getContext(): array { return $this->context; } /** * Get user agent safely */ protected function getUserAgent(): ?string { try { return request()->userAgent(); } catch (\Exception $e) { return null; } } /** * Get IP address safely */ protected function getIpAddress(): ?string { try { return request()->ip(); } catch (\Exception $e) { return null; } } /** * Get user ID safely */ protected function getUserId(): ?int { try { return Auth::id(); } catch (\Exception $e) { return null; } } /** * Log to file safely */ protected function logToFile(string $message, array $context, string $level): void { try { Log::{$level}($message, $context); } catch (\Exception $e) { // Silently fail if logging isn't available } } }