namespace App\Services\Api; use App\Models\ApiProvider; use App\Models\ProviderService; use App\Models\Order; use App\Models\Service; use App\Models\ApiBalance; use App\Models\ApiLog; use App\Events\OrderStatusChanged; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use App\Exceptions\ApiException; class ApiServiceManager { protected ApiServiceFactory $factory; protected array $providers = []; public function __construct(ApiServiceFactory $factory) { $this->factory = $factory; } /** * Get provider instance */ protected function getProvider(int $providerId): ApiProviderInterface { if (!isset($this->providers[$providerId])) { $provider = ApiProvider::findOrFail($providerId); $this->providers[$providerId] = $this->factory->create($provider); } return $this->providers[$providerId]; } /** * Check all providers balance */ public function checkAllBalances(): array { $results = []; $providers = ApiProvider::where('status', 'active')->get(); foreach ($providers as $provider) { try { $results[$provider->id] = $this->checkProviderBalance($provider); } catch (\Exception $e) { Log::error("Failed to check balance for provider {$provider->id}: " . $e->getMessage()); $results[$provider->id] = [ 'success' => false, 'error' => $e->getMessage() ]; } } return $results; } /** * Check specific provider balance */ public function checkProviderBalance(ApiProvider $provider): array { try { $api = $this->getProvider($provider->id); $result = $api->getBalance(); if ($result['success']) { // Store balance history ApiBalance::create([ 'api_provider_id' => $provider->id, 'balance' => $result['balance'], 'currency' => $result['currency'] ?? 'USD', 'checked_at' => now(), ]); // Update provider balance $provider->update([ 'balance' => $result['balance'], 'balance_updated_at' => now(), ]); return [ 'success' => true, 'balance' => $result['balance'], 'currency' => $result['currency'] ?? 'USD', ]; } return $result; } catch (\Exception $e) { throw new ApiException("Balance check failed: " . $e->getMessage()); } } /** * Sync services from all providers */ public function syncAllServices(): array { $results = []; $providers = ApiProvider::where('status', 'active') ->where('auto_sync', true) ->get(); foreach ($providers as $provider) { try { $results[$provider->id] = $this->syncProviderServices($provider); } catch (\Exception $e) { Log::error("Failed to sync services for provider {$provider->id}: " . $e->getMessage()); $results[$provider->id] = [ 'success' => false, 'error' => $e->getMessage() ]; } } return $results; } /** * Sync services from specific provider */ public function syncProviderServices(ApiProvider $provider): array { DB::beginTransaction(); try { $api = $this->getProvider($provider->id); $result = $api->getServices(); if (!$result['success']) { throw new ApiException($result['error'] ?? 'Failed to fetch services'); } $synced = 0; $updated = 0; $failed = 0; foreach ($result['services'] as $serviceData) { try { // Find or create provider service $providerService = ProviderService::updateOrCreate( [ 'api_provider_id' => $provider->id, 'provider_service_id' => $serviceData['provider_service_id'], ], [ 'provider_service_name' => $serviceData['name'], 'provider_price' => $serviceData['price'], 'our_price' => $serviceData['our_price'], 'min_order' => $serviceData['min_order'], 'max_order' => $serviceData['max_order'], 'service_type' => $serviceData['type'] ?? 'default', 'status' => 'active', 'last_sync_at' => now(), ] ); // Update or create associated service if ($providerService->service_id) { // Update existing service $service = Service::find($providerService->service_id); if ($service) { $service->update([ 'price' => $serviceData['our_price'], 'min_order' => $serviceData['min_order'], 'max_order' => $serviceData['max_order'], 'status' => 'active', ]); $updated++; } } else { // Create new service $service = Service::create([ 'name' => $serviceData['name'], 'slug' => \Str::slug($serviceData['name']) . '-' . uniqid(), 'category_id' => $this->getOrCreateCategory($serviceData['category']), 'price' => $serviceData['our_price'], 'min_order' => $serviceData['min_order'], 'max_order' => $serviceData['max_order'], 'service_type' => $serviceData['type'] ?? 'default', 'api_provider_id' => $provider->id, 'api_service_id' => $serviceData['provider_service_id'], 'profit_type' => $provider->profit_type, 'profit_value' => $provider->profit_value, 'profit_margin' => $provider->profit_margin, 'status' => 'active', ]); $providerService->update(['service_id' => $service->id]); $synced++; } } catch (\Exception $e) { Log::error("Failed to sync service: " . $e->getMessage()); $failed++; } } DB::commit(); // Update provider last sync $provider->update(['last_sync_at' => now()]); return [ 'success' => true, 'synced' => $synced, 'updated' => $updated, 'failed' => $failed, ]; } catch (\Exception $e) { DB::rollBack(); throw $e; } } /** * Place order through provider */ public function placeOrder(Order $order): array { try { $provider = ApiProvider::findOrFail($order->api_provider_id); $api = $this->getProvider($provider->id); // Prepare order parameters $params = [ 'service_id' => $order->api_service_id, 'link' => $order->link, 'quantity' => $order->quantity, 'custom_fields' => $order->custom_fields, ]; // Place order $result = $api->placeOrder($params); if (!$result['success']) { throw new ApiException($result['error'] ?? 'Failed to place order'); } // Update order with provider response $order->update([ 'api_order_id' => $result['order_id'], 'status' => $result['status'], 'start_counter' => $result['start_count'] ?? 0, 'remains' => $result['remains'] ?? 0, 'processed_at' => now(), ]); // Create order log $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'api_order_placed', 'description' => 'Order placed with provider', 'new_status' => $result['status'], ]); return [ 'success' => true, 'order_id' => $order->id, 'api_order_id' => $result['order_id'], 'status' => $result['status'], ]; } catch (\Exception $e) { // Mark order as error $order->update(['status' => 'error']); // Log error $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'api_order_failed', 'description' => 'Failed to place order: ' . $e->getMessage(), 'new_status' => 'error', ]); throw new ApiException("Order placement failed: " . $e->getMessage()); } } /** * Update order status from provider */ public function updateOrderStatus(Order $order): array { if (!$order->api_order_id || !$order->api_provider_id) { return ['success' => false, 'message' => 'Order not linked to provider']; } try { $api = $this->getProvider($order->api_provider_id); $result = $api->getOrderStatus($order->api_order_id); if (!$result['success']) { return ['success' => false, 'error' => $result['error'] ?? 'Failed to get status']; } $oldStatus = $order->status; // Check if status changed if ($oldStatus !== $result['status']) { $order->update([ 'status' => $result['status'], 'start_counter' => $result['start_count'] ?? $order->start_counter, 'remains' => $result['remains'] ?? $order->remains, 'completed_at' => $result['completed_at'] ?? ($result['status'] === 'completed' ? now() : null), ]); // Create order log $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'status_updated', 'old_status' => $oldStatus, 'new_status' => $result['status'], 'description' => 'Status updated via API', ]); // Dispatch event event(new OrderStatusChanged($order, $oldStatus, $result['status'])); // Handle auto refund if failed if ($result['status'] === 'error' && $this->shouldAutoRefund($order)) { $this->processAutoRefund($order); } } return [ 'success' => true, 'old_status' => $oldStatus, 'new_status' => $result['status'], ]; } catch (\Exception $e) { Log::error("Failed to update order status for order {$order->id}: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Request order refill */ public function requestRefill(Order $order): array { if (!$order->api_order_id || !$order->api_provider_id) { throw new ApiException('Order not linked to provider'); } try { $api = $this->getProvider($order->api_provider_id); $result = $api->requestRefill($order->api_order_id); if (!$result['success']) { throw new ApiException($result['error'] ?? 'Failed to request refill'); } // Create refill record $refill = $order->refills()->create([ 'refill_id' => 'REF-' . uniqid(), 'user_id' => $order->user_id, 'service_id' => $order->service_id, 'quantity' => $order->quantity, 'status' => 'processing', 'api_refill_id' => $result['refill_id'] ?? null, ]); // Create order log $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'refill_requested', 'description' => 'Refill requested via API', ]); return [ 'success' => true, 'refill_id' => $refill->refill_id, ]; } catch (\Exception $e) { throw new ApiException("Refill request failed: " . $e->getMessage()); } } /** * Cancel order */ public function cancelOrder(Order $order): array { if (!$order->api_order_id || !$order->api_provider_id) { throw new ApiException('Order not linked to provider'); } try { $api = $this->getProvider($order->api_provider_id); $result = $api->cancelOrder($order->api_order_id); if (!$result['success']) { throw new ApiException($result['error'] ?? 'Failed to cancel order'); } // Update order $order->update([ 'status' => 'cancelled', 'cancelled_at' => now(), ]); // Create cancellation record $order->cancellations()->create([ 'user_id' => $order->user_id, 'service_id' => $order->service_id, 'status' => 'approved', 'amount_refunded' => $result['refund_amount'] ?? 0, ]); // Create order log $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'cancelled', 'old_status' => $order->status, 'new_status' => 'cancelled', 'description' => 'Order cancelled via API', ]); return [ 'success' => true, 'refund_amount' => $result['refund_amount'] ?? 0, ]; } catch (\Exception $e) { throw new ApiException("Order cancellation failed: " . $e->getMessage()); } } /** * Process auto refund for failed orders */ protected function processAutoRefund(Order $order): void { try { DB::beginTransaction(); // Calculate refund amount $refundAmount = $order->price; // Create refund transaction $transaction = $order->user->creditWallet( $refundAmount, 'Auto refund for failed order #' . $order->order_id, 'refund', $order ); // Update order $order->update([ 'status' => 'refunded', 'refunded_at' => now(), ]); // Create cancellation record $order->cancellations()->create([ 'user_id' => $order->user_id, 'service_id' => $order->service_id, 'status' => 'approved', 'amount_refunded' => $refundAmount, ]); // Create order log $order->logs()->create([ 'user_id' => $order->user_id, 'action' => 'auto_refunded', 'old_status' => 'error', 'new_status' => 'refunded', 'description' => "Auto refund processed: {$refundAmount}", ]); DB::commit(); } catch (\Exception $e) { DB::rollBack(); Log::error("Auto refund failed for order {$order->id}: " . $e->getMessage()); } } /** * Check if order should be auto refunded */ protected function shouldAutoRefund(Order $order): bool { // Check if auto refund is enabled in settings $autoRefundEnabled = setting('auto_refund_enabled', false); if (!$autoRefundEnabled) { return false; } // Check if order is eligible for refund $maxRefundHours = setting('auto_refund_hours', 24); return $order->created_at->diffInHours(now()) <= $maxRefundHours; } /** * Get or create category */ protected function getOrCreateCategory(?string $categoryName): ?int { if (!$categoryName) { return null; } $category = \App\Models\Category::firstOrCreate( ['name' => $categoryName], [ 'slug' => \Str::slug($categoryName), 'status' => 'active', ] ); return $category->id; } }