Laravel + Cloud2Pdf

Integração completa com API assíncrona para geração de PDFs em Laravel

⚡ Async/Queue Ready 🪝 Webhook Support 📦 Service Class

🚀 Quick Start

1

Instalar Dependências

composer require guzzlehttp/guzzle
2

Configurar .env

CLOUD2PDF_API_URL=https://api.cloud2pdf.com/v1 CLOUD2PDF_API_TOKEN=seu_token_aqui CLOUD2PDF_WEBHOOK_URL=${APP_URL}/webhooks/cloud2pdf CLOUD2PDF_WEBHOOK_SECRET=seu_secret_aqui
3

Criar Service Class

Copie o código da seção Service Class abaixo

4

Usar no Controller

$pdfService = app(\App\Services\Cloud2PdfService::class); $result = $pdfService->generateAsync($html, 'invoice-123');

📦 Service Class Completa

Crie o arquivo app/Services/Cloud2PdfService.php

<?php namespace App\Services; use GuzzleHttp\Client; use Illuminate\Support\Facades\Log; class Cloud2PdfService { protected Client $client; protected string $apiUrl; protected string $apiToken; public function __construct() { $this->apiUrl = config('services.cloud2pdf.api_url'); $this->apiToken = config('services.cloud2pdf.api_token'); $this->client = new Client([ 'base_uri' => $this->apiUrl, 'timeout' => 30, 'headers' => [ 'Authorization' => 'Bearer ' . $this->apiToken, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ] ]); } /** * Gera PDF de forma assíncrona * * @param string $html Conteúdo HTML * @param string|null $reference Sua referência única * @param array $options Opções do PDF (format, landscape, margins, etc) * @param bool $useCdn Salvar no CDN * @param string|null $requestWebhookUrl Webhook opcional para esta requisição * @return array ['pdf_code', 'job_id', 'status', 'reference'] */ public function generateAsync( string $html, ?string $reference = null, array $options = [], bool $useCdn = true, ?string $requestWebhookUrl = null ): array { try { $response = $this->client->post('/pdf/generate-async', [ 'json' => [ 'html' => $html, 'is_disk' => $useCdn, 'reference' => $reference, 'webhook_url' => $requestWebhookUrl, 'options' => array_merge([ 'format' => 'A4', 'landscape' => false, ], $options) ] ]); $data = json_decode($response->getBody(), true); Log::info('PDF generation started', [ 'pdf_code' => $data['pdf_code'], 'reference' => $reference ]); return $data; } catch (\Exception $e) { Log::error('PDF generation failed', [ 'error' => $e->getMessage(), 'reference' => $reference ]); throw $e; } } /** * Verifica status do PDF * * @param string $pdfCode * @return array Status do PDF */ public function checkStatus(string $pdfCode): array { $response = $this->client->get("/pdf/status/{$pdfCode}"); return json_decode($response->getBody(), true); } /** * Aguarda conclusão do PDF (polling) * * @param string $pdfCode * @param int $maxAttempts Máximo de tentativas (default: 60 = 5 minutos) * @param int $delaySeconds Delay entre tentativas (default: 5s) * @return array Dados do PDF completo */ public function waitForCompletion( string $pdfCode, int $maxAttempts = 60, int $delaySeconds = 5 ): array { for ($i = 0; $i < $maxAttempts; $i++) { $status = $this->checkStatus($pdfCode); if ($status['status'] === 'completed') { return $status; } if ($status['status'] === 'failed') { throw new \Exception('PDF generation failed: ' . ($status['error'] ?? 'Unknown error')); } sleep($delaySeconds); } throw new \Exception('PDF generation timeout'); } /** * Baixa PDF como conteúdo binário * * @param string $pdfCode * @return string Conteúdo binário do PDF */ public function download(string $pdfCode): string { $response = $this->client->get("/pdf/download/{$pdfCode}"); return $response->getBody()->getContents(); } /** * Baixa e salva PDF localmente * * @param string $pdfCode * @param string $savePath Caminho onde salvar (ex: storage/app/pdfs/invoice.pdf) * @return string Caminho do arquivo salvo */ public function downloadAndSave(string $pdfCode, string $savePath): string { $content = $this->download($pdfCode); $directory = dirname($savePath); if (!file_exists($directory)) { mkdir($directory, 0755, true); } file_put_contents($savePath, $content); return $savePath; } /** * Gera PDF síncrono (base64) * Use apenas para PDFs pequenos! * * @param string $html * @param array $options * @return array ['pdf' => base64, 'metadata' => [...]] */ public function generateSync(string $html, array $options = []): array { $response = $this->client->post('/pdf/generate', [ 'json' => [ 'html' => $html, 'is_disk' => false, 'options' => $options ] ]); return json_decode($response->getBody(), true); } }

⚙️ Configuração

Adicione em config/services.php

'cloud2pdf' => [ 'api_url' => env('CLOUD2PDF_API_URL', 'https://api.cloud2pdf.com/v1'), 'api_token' => env('CLOUD2PDF_API_TOKEN'), 'webhook_url' => env('CLOUD2PDF_WEBHOOK_URL'), 'webhook_secret' => env('CLOUD2PDF_WEBHOOK_SECRET'), ],

🎮 Exemplo de Controller

<?php namespace App\Http\Controllers; use App\Services\Cloud2PdfService; use Illuminate\Http\Request; class InvoiceController extends Controller { protected Cloud2PdfService $pdfService; public function __construct(Cloud2PdfService $pdfService) { $this->pdfService = $pdfService; } /** * Gerar PDF assíncrono */ public function generatePdf(Request $request) { $invoice = Invoice::findOrFail($request->invoice_id); // Renderizar HTML da invoice $html = view('invoices.pdf', compact('invoice'))->render(); // Gerar PDF assíncrono $result = $this->pdfService->generateAsync( $html, "invoice-{$invoice->id}", // Reference [ 'format' => 'A4', 'landscape' => false, 'margins' => [ 'top' => '20mm', 'right' => '15mm', 'bottom' => '20mm', 'left' => '15mm', ] ] ); // Salvar info no banco $invoice->update([ 'pdf_code' => $result['pdf_code'], 'pdf_status' => 'processing', ]); return response()->json([ 'message' => 'PDF generation started', 'pdf_code' => $result['pdf_code'], 'status_url' => route('invoice.pdf.status', $invoice->id) ]); } /** * Verificar status */ public function checkStatus($invoiceId) { $invoice = Invoice::findOrFail($invoiceId); $status = $this->pdfService->checkStatus($invoice->pdf_code); // Atualizar status no banco if ($status['status'] === 'completed') { $invoice->update([ 'pdf_status' => 'completed', 'pdf_url' => $status['public_link']['url'] ?? null, 'pdf_size' => $status['metadata']['file_size'] ?? null, ]); } return response()->json($status); } /** * Baixar PDF */ public function download($invoiceId) { $invoice = Invoice::findOrFail($invoiceId); $pdfContent = $this->pdfService->download($invoice->pdf_code); return response($pdfContent, 200, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => "attachment; filename=\"invoice-{$invoice->id}.pdf\"", ]); } }

⚙️ Job para Processamento Assíncrono

Crie: php artisan make:job GenerateInvoicePdf

<?php namespace App\Jobs; use App\Models\Invoice; use App\Services\Cloud2PdfService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class GenerateInvoicePdf implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 3; public $timeout = 600; // 10 minutos protected Invoice $invoice; public function __construct(Invoice $invoice) { $this->invoice = $invoice; } public function handle(Cloud2PdfService $pdfService) { // Renderizar HTML $html = view('invoices.pdf', ['invoice' => $this->invoice])->render(); // Gerar PDF assíncrono $result = $pdfService->generateAsync( $html, "invoice-{$this->invoice->id}" ); // Salvar PDF code $this->invoice->update([ 'pdf_code' => $result['pdf_code'], 'pdf_status' => 'processing', ]); // Opcional: Aguardar conclusão try { $completed = $pdfService->waitForCompletion($result['pdf_code']); $this->invoice->update([ 'pdf_status' => 'completed', 'pdf_url' => $completed['public_link']['url'] ?? null, 'pdf_size' => $completed['metadata']['file_size'], ]); // Opcional: Baixar e salvar localmente $localPath = "pdfs/invoices/invoice-{$this->invoice->id}.pdf"; $pdfService->downloadAndSave( $result['pdf_code'], storage_path("app/{$localPath}") ); $this->invoice->update(['pdf_local_path' => $localPath]); } catch (\Exception $e) { $this->invoice->update(['pdf_status' => 'failed']); throw $e; } } } // Uso no Controller: // GenerateInvoicePdf::dispatch($invoice);

🪝 Webhook Handler

Receba notificações automáticas quando o PDF estiver pronto

1. Criar Controller

<?php namespace App\Http\Controllers\Webhooks; use App\Http\Controllers\Controller; use App\Models\Invoice; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class Cloud2PdfWebhookController extends Controller { public function handle(Request $request) { // Validar signature do webhook $signature = $request->header('X-Webhook-Signature'); $secret = config('services.cloud2pdf.webhook_secret'); $expectedSignature = hash_hmac('sha256', $request->getContent(), $secret); if (!hash_equals($expectedSignature, $signature)) { Log::warning('Invalid webhook signature'); return response()->json(['error' => 'Invalid signature'], 401); } // Processar evento $event = $request->input('event'); // "pdf.generated" $status = $request->input('status'); // "completed" ou "failed" $reference = $request->input('reference'); // "invoice-123" $data = $request->input('data', []); Log::info('Webhook received', [ 'event' => $event, 'status' => $status, 'reference' => $reference ]); if ($event === 'pdf.generated' && $status === 'completed') { // Extrair ID da invoice da reference $invoiceId = str_replace('invoice-', '', $reference); $invoice = Invoice::find($invoiceId); if ($invoice) { $invoice->update([ 'pdf_status' => 'completed', 'pdf_url' => $data['download_url'] ?? null, 'pdf_size' => $data['file_size'] ?? null, 'credits_used' => $data['credits_used'] ?? null, ]); // Enviar email para cliente // Mail::to($invoice->customer_email)->send(new InvoiceReady($invoice)); } } if ($event === 'pdf.generated' && $status === 'failed') { $invoiceId = str_replace('invoice-', '', $reference); Invoice::where('id', $invoiceId)->update(['pdf_status' => 'failed']); } return response()->json(['success' => true]); } }

2. Registrar Rota

Em routes/api.php:

Route::post('/webhooks/cloud2pdf', [Cloud2PdfWebhookController::class, 'handle']);

3. Excluir de CSRF

Em app/Http/Middleware/VerifyCsrfToken.php:

protected $except = [ 'api/webhooks/cloud2pdf', ];

🔧 Configurar Webhook: Acesse https://web.9code.work/tokens e configure a URL: https://cloud2pdf.com/api/webhooks/cloud2pdf

🔄 Fluxo Completo de Integração

1

Usuário solicita PDF

Controller chama $pdfService->generateAsync()

2

API retorna pdf_code imediatamente

Salva pdf_code no banco com status "processing"

3

Cloud2Pdf processa o PDF em background

Sistema otimizado para PDFs grandes (até 24MB testado)

4

Webhook notifica conclusão

Seu webhook handler atualiza status e envia email ao cliente

5

Cliente acessa link público ou baixa

PDF disponível via link público ou endpoint de download

💡 Dicas e Melhores Práticas

Use sempre is_disk: true

PDFs são salvos no Bunny CDN com download ultra-rápido

Configure webhooks ao invés de polling

Economize requests e tenha notificações em tempo real

Use Laravel Queue para processamento

Dispatch jobs para não bloquear requests HTTP

Sempre passe uma reference única

Facilita rastreamento e associação com seus registros

PDFs grandes são otimizados automaticamente

Sistema detecta HTML >1MB e aplica configurações especiais