<?php namespace App\Controllers; use App\Integrations\Celcoin\CelcoinChargeService; use App\Integrations\Celcoin\CelcoinSubscriptionService; use App\Integrations\Celcoin\CelcoinWebhookService; use App\Integrations\Celcoin\CelcoinReconciliationService; use Exception; /** * Controlador para integração com Celcoin * Gerencia endpoints de pagamento, webhooks e reconciliação */ class CelcoinController extends BaseController { private ?int $companyId = null; private ?int $gatewaySettingId = null; public function __construct() { parent::__construct(); if (Session::getCompanyId()) { $this->companyId = Session::getCompanyId(); $this->gatewaySettingId = $this->getGatewaySettingId(); } } /** * Cria uma nova cobrança */ public function createCharge() { try { $this->requireMethod('POST'); $this->requireAuth(); $data = $this->getJsonInput(); // Validar dados obrigatórios $this->validateChargeData($data); $chargeService = new CelcoinChargeService($this->companyId, $this->gatewaySettingId); $result = $chargeService->createCharge($data); if ($result['success']) { $this->jsonResponse($result, 201); } else { $this->jsonResponse(['error' => 'Failed to create charge', 'details' => $result], 400); } } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Consulta status de uma cobrança */ public function getChargeStatus($chargeId) { try { $this->requireAuth(); $chargeService = new CelcoinChargeService($this->companyId, $this->gatewaySettingId); $result = $chargeService->getChargeStatus($chargeId); if ($result) { $this->jsonResponse($result); } else { $this->jsonResponse(['error' => 'Charge not found'], 404); } } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Lista cobranças com filtros */ public function listCharges() { try { $this->requireAuth(); $filters = $_GET; $page = (int)($_GET['page'] ?? 1); $limit = (int)($_GET['limit'] ?? 50); $chargeService = new CelcoinChargeService($this->companyId, $this->gatewaySettingId); $result = $chargeService->listCharges($filters, $page, $limit); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Cancela uma cobrança */ public function cancelCharge($chargeId) { try { $this->requireMethod('POST'); $this->requireAuth(); $chargeService = new CelcoinChargeService($this->companyId, $this->gatewaySettingId); $result = $chargeService->cancelCharge($chargeId); if ($result) { $this->jsonResponse(['success' => true, 'message' => 'Charge canceled']); } else { $this->jsonResponse(['error' => 'Failed to cancel charge'], 400); } } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Cria uma nova assinatura */ public function createSubscription() { try { $this->requireMethod('POST'); $this->requireAuth(); $data = $this->getJsonInput(); // Validar dados obrigatórios $this->validateSubscriptionData($data); $subscriptionService = new CelcoinSubscriptionService($this->companyId, $this->gatewaySettingId); $result = $subscriptionService->createSubscription($data); if ($result['success']) { $this->jsonResponse($result, 201); } else { $this->jsonResponse(['error' => 'Failed to create subscription', 'details' => $result], 400); } } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Cancela uma assinatura */ public function cancelSubscription($subscriptionId) { try { $this->requireMethod('POST'); $this->requireAuth(); $data = $this->getJsonInput(); $reason = $data['reason'] ?? null; $subscriptionService = new CelcoinSubscriptionService($this->companyId, $this->gatewaySettingId); $result = $subscriptionService->cancelSubscription($subscriptionId, $reason); if ($result) { $this->jsonResponse(['success' => true, 'message' => 'Subscription canceled']); } else { $this->jsonResponse(['error' => 'Failed to cancel subscription'], 400); } } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Lista assinaturas */ public function listSubscriptions() { try { $this->requireAuth(); $filters = $_GET; $page = (int)($_GET['page'] ?? 1); $limit = (int)($_GET['limit'] ?? 50); $subscriptionService = new CelcoinSubscriptionService($this->companyId, $this->gatewaySettingId); $result = $subscriptionService->listSubscriptions($filters, $page, $limit); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Endpoint de webhook da Celcoin */ public function webhook() { try { $this->requireMethod('POST'); $headers = $this->getAllHeaders(); $this->resolveWebhookContext($headers); $webhookData = $this->getJsonInput(); $webhookService = new CelcoinWebhookService($this->companyId, $this->gatewaySettingId); $result = $webhookService->processWebhook($webhookData, $headers); if ($result['success']) { $this->jsonResponse(['status' => 'ok'], 200); } else { $this->jsonResponse(['error' => 'Webhook processing failed'], 400); } } catch (Exception $e) { // Log do erro mas não expor detalhes error_log('Celcoin webhook error: ' . $e->getMessage()); $this->jsonResponse(['error' => 'Internal server error'], 500); } } /** * Executa reconciliação de cobranças */ public function reconcileCharges() { try { $this->requireMethod('POST'); $this->requireAuth(); $this->requireAdmin(); $reconciliationService = new CelcoinReconciliationService($this->companyId, $this->gatewaySettingId); $result = $reconciliationService->syncPendingCharges(); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Executa reconciliação de assinaturas */ public function reconcileSubscriptions() { try { $this->requireMethod('POST'); $this->requireAuth(); $this->requireAdmin(); $reconciliationService = new CelcoinReconciliationService($this->companyId, $this->gatewaySettingId); $result = $reconciliationService->syncActiveSubscriptions(); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Executa reconciliação completa */ public function fullReconciliation() { try { $this->requireMethod('POST'); $this->requireAuth(); $this->requireAdmin(); $reconciliationService = new CelcoinReconciliationService($this->companyId, $this->gatewaySettingId); $result = $reconciliationService->fullReconciliation(); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Lista logs de sincronização */ public function listSyncLogs() { try { $this->requireAuth(); $this->requireAdmin(); $filters = $_GET; $page = (int)($_GET['page'] ?? 1); $limit = (int)($_GET['limit'] ?? 50); $reconciliationService = new CelcoinReconciliationService($this->companyId, $this->gatewaySettingId); $result = $reconciliationService->listSyncLogs($filters, $page, $limit); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Lista webhooks */ public function listWebhooks() { try { $this->requireAuth(); $this->requireAdmin(); $filters = $_GET; $page = (int)($_GET['page'] ?? 1); $limit = (int)($_GET['limit'] ?? 50); $webhookService = new CelcoinWebhookService($this->companyId, $this->gatewaySettingId); $result = $webhookService->listWebhooks($filters, $page, $limit); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Reprocessa webhook */ public function reprocessWebhook($webhookLogId) { try { $this->requireMethod('POST'); $this->requireAuth(); $this->requireAdmin(); $webhookService = new CelcoinWebhookService($this->companyId, $this->gatewaySettingId); $result = $webhookService->reprocessWebhook((int)$webhookLogId); $this->jsonResponse($result); } catch (Exception $e) { $this->jsonResponse(['error' => $e->getMessage()], 400); } } /** * Valida dados da cobrança */ private function validateChargeData(array $data): void { $required = ['amount', 'description']; foreach ($required as $field) { if (!isset($data[$field]) || empty($data[$field])) { throw new Exception("Missing required field: {$field}"); } } if (!is_numeric($data['amount']) || $data['amount'] <= 0) { throw new Exception('Amount must be a positive number'); } } /** * Valida dados da assinatura */ private function validateSubscriptionData(array $data): void { $required = ['user_id', 'amount', 'billing_cycle']; foreach ($required as $field) { if (!isset($data[$field]) || empty($data[$field])) { throw new Exception("Missing required field: {$field}"); } } if (!is_numeric($data['amount']) || $data['amount'] <= 0) { throw new Exception('Amount must be a positive number'); } $validCycles = ['daily', 'weekly', 'monthly', 'quarterly', 'semiannual', 'annual']; if (!in_array($data['billing_cycle'], $validCycles)) { throw new Exception('Invalid billing cycle'); } } /** * Obtém company_id do contexto */ private function getCompanyId(): int { if ($this->companyId) { return $this->companyId; } $companyId = Session::getCompanyId(); if (!$companyId) { throw new Exception('Company context is missing'); } $this->companyId = $companyId; return $companyId; } /** * Obtém gateway_setting_id */ private function getGatewaySettingId(): int { if ($this->gatewaySettingId) { return $this->gatewaySettingId; } $companyId = $this->getCompanyId(); $db = \Database::getInstance()->getConnection(); $stmt = $db->prepare( 'SELECT cgs.id FROM company_gateway_settings cgs ' . 'JOIN payment_gateways pg ON pg.id = cgs.gateway_id ' . 'WHERE cgs.company_id = ? AND pg.code = ? AND cgs.is_active = 1 ' . 'LIMIT 1' ); $stmt->execute([$companyId, 'celcoin']); $row = $stmt->fetch(); if (!$row) { throw new Exception('Celcoin gateway configuration not found for this company'); } $this->gatewaySettingId = (int)$row['id']; return $this->gatewaySettingId; } /** * Resolve o contexto do webhook público da Celcoin */ private function resolveWebhookContext(array $headers): void { if ($this->companyId && $this->gatewaySettingId) { return; } $companyId = Session::getCompanyId(); if ($companyId) { $this->companyId = $companyId; $this->gatewaySettingId = $this->getGatewaySettingId(); return; } $signature = $headers['x-celcoin-signature'] ?? $headers['X-Celcoin-Signature'] ?? null; if (!$signature) { throw new Exception('Webhook signature is required to resolve company context'); } $db = \Database::getInstance()->getConnection(); $stmt = $db->prepare( 'SELECT cgs.id AS gateway_setting_id, cgs.company_id ' . 'FROM company_gateway_settings cgs ' . 'JOIN payment_gateways pg ON pg.id = cgs.gateway_id ' . 'WHERE pg.code = ? AND cgs.webhook_secret = ? AND cgs.is_active = 1 ' . 'LIMIT 1' ); $stmt->execute(['celcoin', $signature]); $row = $stmt->fetch(); if (!$row) { throw new Exception('Unable to resolve company from webhook secret'); } $this->companyId = (int)$row['company_id']; $this->gatewaySettingId = (int)$row['gateway_setting_id']; } /** * Obtém todos os headers da requisição */ private function getAllHeaders(): array { $headers = []; foreach ($_SERVER as $key => $value) { if (strpos($key, 'HTTP_') === 0) { $headerKey = str_replace('HTTP_', '', $key); $headerKey = str_replace('_', '-', $headerKey); $headers[strtolower($headerKey)] = $value; } } return $headers; } }