<?php namespace App\Integrations\Celcoin; use App\Models\CelcoinCharge; use Exception; /** * Serviço de gerenciamento de cobranças na Celcoin * Cria e gerencia cobranças avulsas (PIX, boleto, cartão) */ class CelcoinChargeService { private CelcoinHttpClient $httpClient; private CelcoinAuthService $authService; private CelcoinCustomerService $customerService; private int $companyId; private int $gatewaySettingId; public function __construct(int $companyId, int $gatewaySettingId) { $this->companyId = $companyId; $this->gatewaySettingId = $gatewaySettingId; $this->authService = new CelcoinAuthService($companyId, $gatewaySettingId); $this->httpClient = new CelcoinHttpClient($companyId); $this->customerService = new CelcoinCustomerService($companyId, $gatewaySettingId); } /** * Cria uma nova cobrança */ public function createCharge(array $chargeData): array { try { // Validar dados obrigatórios $this->validateChargeData($chargeData); // Sincronizar cliente se necessário $customerId = $this->ensureCustomerExists($chargeData['user_id']); // Obter token válido $accessToken = $this->authService->getValidAccessToken(); $this->httpClient->setAccessToken($accessToken); // Preparar dados para a API da Celcoin $apiData = $this->prepareChargeData($chargeData, $customerId); // Determinar endpoint baseado no método de pagamento $endpoint = $this->getChargeEndpoint($chargeData['payment_method']); // Criar cobrança na Celcoin $response = $this->httpClient->post($endpoint, $apiData); if (!$response['success']) { throw new Exception('Failed to create charge in Celcoin API'); } $celcoinCharge = $response['data']; // Salvar cobrança no banco local $localChargeId = $this->saveCharge($chargeData, $celcoinCharge); // Log de sucesso $this->logChargeSuccess('Charge created', $localChargeId, $celcoinCharge['id']); return [ 'success' => true, 'charge_id' => $localChargeId, 'celcoin_charge_id' => $celcoinCharge['id'], 'payment_data' => $this->extractPaymentData($celcoinCharge, $chargeData['payment_method']) ]; } catch (Exception $e) { $this->logChargeError('Charge creation failed', $chargeData['user_id'], $e->getMessage()); throw new Exception('Failed to create charge: ' . $e->getMessage()); } } /** * Consulta status de uma cobrança */ public function getChargeStatus(string $celcoinChargeId): ?array { try { $accessToken = $this->authService->getValidAccessToken(); $this->httpClient->setAccessToken($accessToken); $response = $this->httpClient->get("/v5/charges/{$celcoinChargeId}"); if (!$response['success']) { return null; } return $response['data']; } catch (Exception $e) { $this->logChargeError('Charge status check failed', 0, $e->getMessage()); return null; } } /** * Cancela uma cobrança */ public function cancelCharge(string $celcoinChargeId): bool { try { $accessToken = $this->authService->getValidAccessToken(); $this->httpClient->setAccessToken($accessToken); $response = $this->httpClient->delete("/v5/charges/{$celcoinChargeId}"); if ($response['success']) { // Atualizar status local $this->updateChargeStatus($celcoinChargeId, 'canceled'); $this->logChargeSuccess('Charge canceled', 0, $celcoinChargeId); return true; } return false; } catch (Exception $e) { $this->logChargeError('Charge cancellation failed', 0, $e->getMessage()); return false; } } /** * Lista cobranças com filtros */ public function listCharges(array $filters = [], int $page = 1, int $limit = 50): array { try { $accessToken = $this->authService->getValidAccessToken(); $this->httpClient->setAccessToken($accessToken); $params = array_merge($filters, [ 'page' => $page, 'limit' => $limit ]); $response = $this->httpClient->get('/v5/charges?' . http_build_query($params)); if (!$response['success']) { throw new Exception('Failed to list charges'); } return $response['data']; } catch (Exception $e) { $this->logChargeError('Charge list failed', 0, $e->getMessage()); throw $e; } } /** * Processa webhook de cobrança */ public function processChargeWebhook(array $webhookData): array { try { $celcoinChargeId = $webhookData['id'] ?? null; $status = $webhookData['status'] ?? null; if (!$celcoinChargeId || !$status) { throw new Exception('Invalid webhook data: missing charge ID or status'); } // Atualizar status da cobrança $updated = $this->updateChargeStatus($celcoinChargeId, $status, $webhookData); if ($updated) { // Executar ações específicas baseadas no status $this->handleChargeStatusChange($celcoinChargeId, $status, $webhookData); $this->logChargeSuccess('Charge webhook processed', 0, $celcoinChargeId); return ['success' => true, 'processed' => true]; } return ['success' => false, 'processed' => false, 'error' => 'Charge not found']; } catch (Exception $e) { $this->logChargeError('Charge webhook processing failed', 0, $e->getMessage()); return ['success' => false, 'processed' => false, 'error' => $e->getMessage()]; } } /** * Valida dados da cobrança */ private function validateChargeData(array $chargeData): void { $required = ['user_id', 'amount', 'payment_method']; foreach ($required as $field) { if (!isset($chargeData[$field]) || empty($chargeData[$field])) { throw new Exception("Missing required field: {$field}"); } } // Validar método de pagamento $validMethods = ['pix', 'boleto', 'credit_card', 'debit_card']; if (!in_array($chargeData['payment_method'], $validMethods)) { throw new Exception("Invalid payment method: {$chargeData['payment_method']}"); } // Validar valor if (!is_numeric($chargeData['amount']) || $chargeData['amount'] <= 0) { throw new Exception('Invalid amount: must be a positive number'); } } /** * Garante que o cliente existe na Celcoin */ private function ensureCustomerExists(int $userId): string { $result = $this->customerService->syncCustomer($userId); if (!$result['success']) { throw new Exception('Failed to sync customer for charge creation'); } return $result['customer_id']; } /** * Prepara dados da cobrança para a API da Celcoin */ private function prepareChargeData(array $chargeData, string $customerId): array { $apiData = [ 'customerId' => $customerId, 'value' => $chargeData['amount'], 'description' => $chargeData['description'] ?? 'Cobrança avulsa', 'dueDate' => $chargeData['due_date'] ?? date('Y-m-d') ]; // Configurações específicas por método de pagamento switch ($chargeData['payment_method']) { case 'pix': $apiData['paymentMethod'] = 'pix'; break; case 'boleto': $apiData['paymentMethod'] = 'boleto'; if (isset($chargeData['installments'])) { $apiData['installments'] = $chargeData['installments']; } break; case 'credit_card': case 'debit_card': $apiData['paymentMethod'] = $chargeData['payment_method']; if (isset($chargeData['card_token'])) { $apiData['cardToken'] = $chargeData['card_token']; } if (isset($chargeData['installments'])) { $apiData['installments'] = $chargeData['installments']; } break; } return $apiData; } /** * Obtém endpoint correto baseado no método de pagamento */ private function getChargeEndpoint(string $paymentMethod): string { $endpoints = [ 'pix' => '/v5/charges/pix', 'boleto' => '/v5/charges/boleto', 'credit_card' => '/v5/charges/card', 'debit_card' => '/v5/charges/card' ]; return $endpoints[$paymentMethod] ?? '/v5/charges'; } /** * Salva cobrança no banco local */ private function saveCharge(array $chargeData, array $celcoinCharge): int { $chargeModel = new CelcoinCharge(); return $chargeModel->create([ 'company_id' => $this->companyId, 'user_id' => $chargeData['user_id'], 'celcoin_customer_id' => $celcoinCharge['customerId'] ?? null, 'celcoin_charge_id' => $celcoinCharge['id'], 'amount' => $chargeData['amount'], 'currency' => 'BRL', 'description' => $chargeData['description'] ?? null, 'due_date' => $chargeData['due_date'] ?? date('Y-m-d'), 'status' => 'pending', 'payment_method' => json_encode($chargeData['payment_method']), 'installments' => $chargeData['installments'] ?? 1, 'qr_code' => $celcoinCharge['qrCode'] ?? null, 'qr_code_base64' => $celcoinCharge['qrCodeBase64'] ?? null, 'billet_url' => $celcoinCharge['billetUrl'] ?? null, 'billet_base64' => $celcoinCharge['billetBase64'] ?? null, 'reference_id' => $chargeData['reference_id'] ?? null, 'reference_type' => $chargeData['reference_type'] ?? null, 'metadata' => json_encode($chargeData['metadata'] ?? []) ]); } /** * Atualiza status da cobrança */ private function updateChargeStatus(string $celcoinChargeId, string $status, array $webhookData = []): bool { $chargeModel = new CelcoinCharge(); $charge = $chargeModel->findByCelcoinId($this->companyId, $celcoinChargeId); if (!$charge) { return false; } $updateData = [ 'status' => $this->mapCelcoinStatus($status), 'updated_at' => date('Y-m-d H:i:s') ]; // Adicionar data de pagamento se pago if ($status === 'paid' && isset($webhookData['paidAt'])) { $updateData['payment_date'] = date('Y-m-d H:i:s', strtotime($webhookData['paidAt'])); } // Adicionar dados de pagamento se disponíveis if (!empty($webhookData['paymentData'])) { $updateData['payment_method'] = json_encode($webhookData['paymentData']); } return $chargeModel->update($charge['id'], $updateData); } /** * Mapeia status da Celcoin para status local */ private function mapCelcoinStatus(string $celcoinStatus): string { $statusMap = [ 'pending' => 'pending', 'paid' => 'paid', 'expired' => 'expired', 'canceled' => 'canceled', 'failed' => 'failed' ]; return $statusMap[$celcoinStatus] ?? 'pending'; } /** * Extrai dados de pagamento específicos do método */ private function extractPaymentData(array $celcoinCharge, string $paymentMethod): array { $paymentData = []; switch ($paymentMethod) { case 'pix': $paymentData = [ 'qr_code' => $celcoinCharge['qrCode'] ?? null, 'qr_code_base64' => $celcoinCharge['qrCodeBase64'] ?? null, 'expires_at' => $celcoinCharge['expiresAt'] ?? null ]; break; case 'boleto': $paymentData = [ 'billet_url' => $celcoinCharge['billetUrl'] ?? null, 'billet_base64' => $celcoinCharge['billetBase64'] ?? null, 'digitable_line' => $celcoinCharge['digitableLine'] ?? null ]; break; case 'credit_card': case 'debit_card': $paymentData = [ 'card_brand' => $celcoinCharge['cardBrand'] ?? null, 'last_four' => $celcoinCharge['lastFour'] ?? null, 'installments' => $celcoinCharge['installments'] ?? 1 ]; break; } return $paymentData; } /** * Executa ações específicas baseadas na mudança de status */ private function handleChargeStatusChange(string $celcoinChargeId, string $status, array $webhookData): void { // Aqui você pode implementar ações específicas, como: // - Enviar email de confirmação de pagamento // - Atualizar status de agendamento // - Liberar acesso a funcionalidades // - etc. switch ($status) { case 'paid': $this->handleChargePaid($celcoinChargeId, $webhookData); break; case 'expired': $this->handleChargeExpired($celcoinChargeId, $webhookData); break; case 'canceled': $this->handleChargeCanceled($celcoinChargeId, $webhookData); break; } } /** * Trata cobrança paga */ private function handleChargePaid(string $celcoinChargeId, array $webhookData): void { // Implementar lógica específica para cobrança paga // Ex: atualizar saldo do usuário, liberar serviços, etc. } /** * Trata cobrança expirada */ private function handleChargeExpired(string $celcoinChargeId, array $webhookData): void { // Implementar lógica específica para cobrança expirada // Ex: notificar usuário, tentar nova cobrança, etc. } /** * Trata cobrança cancelada */ private function handleChargeCanceled(string $celcoinChargeId, array $webhookData): void { // Implementar lógica específica para cobrança cancelada } /** * Log de sucesso na cobrança */ private function logChargeSuccess(string $action, int $chargeId, string $celcoinId): void { error_log("[Celcoin Charge] {$action} - Charge ID: {$chargeId}, Celcoin ID: {$celcoinId}"); } /** * Log de erro na cobrança */ private function logChargeError(string $action, int $userId, string $error): void { error_log("[Celcoin Charge Error] {$action} - User: {$userId}, Error: {$error}"); } }