cprModel = new CprModel(); $this->statusModel = new StatusModel(); $this->paymentService = new PaymentService(); } public function __invoke(ServerRequestInterface $request) { $body = json_decode((string)$request->getBody(), true) ?? []; try { val::key('cpr_children_codes', val::arrayType()->notEmpty()->each(val::stringType()->notEmpty())) ->assert($body); } catch (ValidationException $e) { return ResponseLib::sendFail( 'Validation failed: ' . $e->getFullMessage(), [], 'E_VALIDATE' )->withStatus(400); } $userId = (int)($request->getAttribute('api_user_id') ?? 0); if ($userId <= 0) { return ResponseLib::sendFail('Authenticated user not found', [], 'E_VALIDATE')->withStatus(401); } $statusId = $this->statusModel->getIdByStatus('pending'); if ($statusId === null) { return ResponseLib::sendFail('Pending status not found', [], 'E_DATABASE')->withStatus(500); } try { $pixData = $this->generateDynamicQrcode(); } catch (\Throwable $e) { return ResponseLib::sendFail('Failed to generate PIX QR Code: ' . $e->getMessage(), [], 'E_INTERNAL')->withStatus(500); } try { $paymentId = $this->paymentService->createPendingPayment($pixData['item_id'], $statusId, $userId); } catch (\Throwable $e) { return ResponseLib::sendFail('Failed to create payment record: ' . $e->getMessage(), [], 'E_DATABASE')->withStatus(500); } try { $record = $this->cprModel->create($body, $statusId, $paymentId); } catch (\InvalidArgumentException $e) { return ResponseLib::sendFail($e->getMessage(), [], 'E_VALIDATE')->withStatus(400); } catch (\Throwable $e) { return ResponseLib::sendFail('Failed to create CPR: ' . $e->getMessage(), [], 'E_DATABASE')->withStatus(500); } return ResponseLib::sendOk([ 'cpr' => $record, 'pix' => [ 'qrcode_url' => $pixData['qrcode_url'], ] ], 'S_CREATED'); } private function generateDynamicQrcode(): array { $cliPath = dirname(__DIR__) . '/bin/genial-cli'; if (!is_file($cliPath) || !is_executable($cliPath)) { throw new \RuntimeException('genial-cli executable not found or not executable'); } $amount = self::PIX_VALUE; $command = sprintf('%s qrcodedynamic %s', escapeshellarg($cliPath), escapeshellarg($amount)); $result = BashExecutor::run($command, 60); if (($result['exitCode'] ?? 1) !== 0) { $this->logCliResult($result, 'genial-cli non-zero exit'); $message = $result['error'] ?: $result['output'] ?: 'Unknown error'; throw new \RuntimeException($message); } $output = $result['output'] ?? ''; try { $parsed = $this->decodeCliOutput($output); } catch (\Throwable $e) { $this->logCliResult($result, 'genial-cli parse failure'); throw $e; } $items = $parsed['data']['items'] ?? null; if (!is_array($items) || empty($items) || !is_array($items[0])) { throw new \RuntimeException('genial-cli output is missing items array'); } $firstItem = $items[0]; $itemId = $firstItem['itemId'] ?? null; $qrcodeUrl = $firstItem['data']['qrcodeURL'] ?? null; if (!$itemId || !$qrcodeUrl) { throw new \RuntimeException('Unable to parse itemId or qrcodeURL from genial-cli output'); } return [ 'item_id' => $itemId, 'qrcode_url' => $qrcodeUrl, ]; } private function decodeCliOutput(string $content): array { $clean = trim($content); if ($clean === '') { throw new \RuntimeException('genial-cli returned empty output'); } $decoded = json_decode($clean, true); if (json_last_error() === JSON_ERROR_NONE) { return $decoded; } $normalized = $this->normalizeCliOutput($clean); $decoded = json_decode($normalized, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \RuntimeException('Failed to decode genial-cli output: ' . json_last_error_msg()); } return $decoded; } private function normalizeCliOutput(string $content): string { // Strip ANSI escape codes, just in case $normalized = preg_replace('/\e\[[\d;]*m/', '', $content); // Convert single-quoted strings to JSON-compatible double-quoted ones $normalized = preg_replace_callback( "/'([^'\\\\]*(?:\\\\.[^'\\\\]*)*)'/", static function (array $matches): string { $inner = str_replace(['\\', '"'], ['\\\\', '\\"'], $matches[1]); return '"' . $inner . '"'; }, $normalized ); // Quote object keys so json_decode can understand them $normalized = preg_replace( '/(?<=\{|\[|,|\n)\s*([A-Za-z_][A-Za-z0-9_]*)\s*:/', '"$1":', $normalized ); return $normalized; } private function logCliResult(array $result, string $context): void { $exitCode = $result['exitCode'] ?? 'null'; $stdout = trim($result['output'] ?? ''); $stderr = trim($result['error'] ?? ''); error_log(sprintf( '[RegisterCprController] %s | exitCode: %s | stdout: %s | stderr: %s', $context, (string)$exitCode, $stdout === '' ? '' : $stdout, $stderr === '' ? '' : $stderr )); } }