Integração completa com API assíncrona para geração de PDFs em Laravel
composer require guzzlehttp/guzzle
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
Copie o código da seção Service Class abaixo
$pdfService = app(\App\Services\Cloud2PdfService::class);
$result = $pdfService->generateAsync($html, 'invoice-123');
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);
}
}
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'),
],
<?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\"",
]);
}
}
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);
Receba notificações automáticas quando o PDF estiver pronto
<?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]);
}
}
Em routes/api.php:
Route::post('/webhooks/cloud2pdf', [Cloud2PdfWebhookController::class, 'handle']);
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
Controller chama $pdfService->generateAsync()
Salva pdf_code no banco com status "processing"
Sistema otimizado para PDFs grandes (até 24MB testado)
Seu webhook handler atualiza status e envia email ao cliente
PDF disponível via link público ou endpoint de download
PDFs são salvos no Bunny CDN com download ultra-rápido
Economize requests e tenha notificações em tempo real
Dispatch jobs para não bloquear requests HTTP
Facilita rastreamento e associação com seus registros
Sistema detecta HTML >1MB e aplica configurações especiais