Laravel Moloni
Integracao completa com a API de faturacao Moloni para Laravel. Gestao de clientes, produtos, faturas e todos os documentos fiscais com type safety total. Agora com cobertura total da API Moloni — 17 novos recursos, Laravel 13 e Pest v4.
Overview
O Laravel Moloni e um pacote que integra a sua aplicacao Laravel com a API do Moloni, a plataforma de faturacao mais utilizada em Portugal. Oferece uma interface fluente e tipada para gerir empresas, clientes, produtos, faturas e todos os outros recursos Moloni, com type safety total atraves de DTOs e enums.
Tipos de documentos suportados
- Faturas
- Recibos
- Notas de Credito
- Notas de Debito
- Faturas Simplificadas
- Fatura-Recibos
- Guias de Remessa
- Guias de Transporte
- Cartas de Porte
- Orcamentos
- Documentos Genericos
Instalacao
Instale o pacote via Composer, publique a configuracao e execute as migrations.
composer require digitaldev-lx/laravel-moloni
php artisan vendor:publish --tag=moloni-config
php artisan migrate
Configuracao
Adicione as credenciais da API Moloni ao ficheiro .env. As credenciais sao obtidas no Moloni Developer Portal.
MOLONI_CLIENT_ID=your-client-id
MOLONI_CLIENT_SECRET=your-client-secret
MOLONI_USERNAME=your-username
MOLONI_PASSWORD=your-password
MOLONI_COMPANY_ID=your-company-id
# Opcional — sandbox ou base URL customizada (v1.1+)
MOLONI_BASE_URL=https://api.moloni.pt/v1/
Variaveis de ambiente
| Variavel | Descricao |
|---|---|
| MOLONI_CLIENT_ID | Client ID da aplicacao registada no Moloni |
| MOLONI_CLIENT_SECRET | Client Secret da aplicacao |
| MOLONI_USERNAME | Email da conta Moloni |
| MOLONI_PASSWORD | Password da conta Moloni |
| MOLONI_COMPANY_ID | ID da empresa no Moloni |
| MOLONI_BASE_URL | Base URL da API (opcional). Util para sandbox ou ambientes de teste. Default: https://api.moloni.pt/v1/ |
Clientes
Gerir clientes atraves da facade Moloni. Pode listar, pesquisar por NIF e criar novos clientes usando DTOs tipados ou arrays simples.
use DigitaldevLx\LaravelMoloni\Facades\Moloni;
$companyId = config('moloni.company_id');
// Listar todos os clientes
$customers = Moloni::customers()->getAll($companyId);
// Pesquisar por NIF
$customer = Moloni::customers()->getByVat($companyId, '123456789');
use DigitaldevLx\LaravelMoloni\DataTransferObjects\Customer as CustomerDto;
$dto = new CustomerDto(
vat: '123456789',
number: 'C001',
name: 'John Doe',
email: 'john@example.com',
address: '123 Main Street',
city: 'Lisbon',
zipCode: '1000-001',
countryId: 1,
);
$customer = Moloni::customers()->insert($companyId, $dto);
$customer = Moloni::customers()->insert($companyId, [
'vat' => '123456789',
'number' => 'C001',
'name' => 'John Doe',
'email' => 'john@example.com',
]);
Produtos
Gestao completa do catalogo de produtos com suporte a categorias, tipos e unidades de medida.
$companyId = config('moloni.company_id');
// Listar todos
$products = Moloni::products()->getAll($companyId);
// Pesquisar por referencia
$product = Moloni::products()->getByReference($companyId, 'PROD-001');
use DigitaldevLx\LaravelMoloni\DataTransferObjects\Product as ProductDto;
use DigitaldevLx\LaravelMoloni\Enums\ProductType;
$dto = new ProductDto(
name: 'Widget',
reference: 'PROD-001',
type: ProductType::Product,
categoryId: 1,
unitId: 1,
price: 29.99,
);
$product = Moloni::products()->insert($companyId, $dto);
Documentos e Faturas
Emita faturas e outros documentos fiscais com DTOs tipados para produtos, pagamentos e datas. O pacote suporta todos os tipos de documentos do Moloni.
use DigitaldevLx\LaravelMoloni\DataTransferObjects\Document as DocumentDto;
use DigitaldevLx\LaravelMoloni\DataTransferObjects\DocumentProduct;
use DigitaldevLx\LaravelMoloni\DataTransferObjects\Payment;
$dto = new DocumentDto(
documentSetId: 1,
customerId: 1,
date: '2026-03-31',
expirationDate: '2026-04-30',
products: [
new DocumentProduct(
productId: 1,
qty: 2,
price: 29.99,
),
],
payments: [
new Payment(
paymentMethodId: 1,
value: 59.98,
date: '2026-03-31',
),
],
);
$invoice = Moloni::invoices()->insert($companyId, $dto);
// A partir de v2.0, getPdfLink() usa o endpoint generico documents/getPDFLink
$pdfLink = Moloni::invoices()->getPdfLink($companyId, $invoiceId);
Todos os tipos de documentos
Cada tipo de documento segue a mesma interface. Substitua invoices() pelo metodo correspondente:
Moloni::invoices() // Faturas
Moloni::receipts() // Recibos
Moloni::creditNotes() // Notas de Credito
Moloni::debitNotes() // Notas de Debito
Moloni::simplifiedInvoices() // Faturas Simplificadas
Moloni::invoiceReceipts() // Fatura-Recibos
Moloni::deliveryNotes() // Guias de Remessa
Moloni::billsOfLading() // Cartas de Porte
Moloni::waybills() // Guias de Transporte
Moloni::estimates() // Orcamentos
Moloni::purchaseOrders() // Ordens de Compra (v2.0+)
Moloni::supplierInvoices() // Faturas de Fornecedor (v2.0+)
Moloni::documents() // Documentos Genericos
Trait HasMoloniDocuments
Associe documentos Moloni a qualquer model Eloquent:
use DigitaldevLx\LaravelMoloni\Concerns\HasMoloniDocuments;
class Order extends Model
{
use HasMoloniDocuments;
}
// Aceder aos documentos
$order->moloniDocuments;
Recursos e Configuracoes
Aceda a todos os recursos auxiliares do Moloni -- impostos, metodos de pagamento, series de documentos, armazens e mais.
$companyId = config('moloni.company_id');
$taxes = Moloni::taxes()->getAll($companyId);
$paymentMethods = Moloni::paymentMethods()->getAll($companyId);
$documentSets = Moloni::documentSets()->getAll($companyId);
$warehouses = Moloni::warehouses()->getAll($companyId);
$units = Moloni::measurementUnits()->getAll($companyId);
$maturityDates = Moloni::maturityDates()->getAll($companyId);
$deliveryMethods = Moloni::deliveryMethods()->getAll($companyId);
$bankAccounts = Moloni::bankAccounts()->getAll($companyId);
$countries = Moloni::countries()->getAll();
$currencies = Moloni::currencies()->getAll();
$languages = Moloni::languages()->getAll();
$exemptions = Moloni::taxExemptions()->getAll();
$fiscalZones = Moloni::fiscalZones()->getAll($countryId);
Novos recursos em v2.0
A v2.0 completa a cobertura da API Moloni com 17 novos recursos:
// Perfil e conta
$profile = Moloni::myProfile()->get();
$subscription = Moloni::subscription()->get();
$users = Moloni::users()->getAll($companyId);
// Compras
$purchaseOrders = Moloni::purchaseOrders()->getAll($companyId);
// Auxiliares
$salesmen = Moloni::salesmen()->getAll($companyId);
$vehicles = Moloni::vehicles()->getAll($companyId);
// Empresa
$slug = Moloni::companies()->freeSlug($companyId, 'my-slug');
Data Transfer Objects
O pacote fornece DTOs tipados no namespace DigitaldevLx\LaravelMoloni\DataTransferObjects para garantir type safety:
Eventos
O pacote emite eventos para todas as mutacoes, permitindo reagir a criacoes, atualizacoes e alteracoes de estado de documentos.
| Evento | Trigger |
|---|---|
| DocumentCreated | Documento criado com sucesso |
| DocumentCancelled | Documento anulado |
| DocumentClosed | Documento fechado/finalizado |
| CustomerCreated | Novo cliente criado |
| CustomerUpdated | Cliente atualizado |
| ProductCreated | Novo produto criado |
| ProductUpdated | Produto atualizado |
| TokenRefreshed | Token OAuth renovado automaticamente |
Registar listeners
Todos os eventos estao no namespace DigitaldevLx\LaravelMoloni\Events.
Auto-discovery (recomendado): Crie uma classe listener em app/Listeners/ — o Laravel descobre-a automaticamente pelo type-hint do metodo handle():
use DigitaldevLx\LaravelMoloni\Events\DocumentCreated;
class SendInvoiceNotification
{
public function handle(DocumentCreated $event): void
{
// $event->data contem a resposta da API
// $event->documentType contem o tipo de documento
}
}
Registo manual: Registe listeners no AppServiceProvider:
use DigitaldevLx\LaravelMoloni\Events\DocumentCreated;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(
DocumentCreated::class,
SendInvoiceNotification::class,
);
}
Closure listeners:
use DigitaldevLx\LaravelMoloni\Events\DocumentCreated;
use Illuminate\Support\Facades\Event;
Event::listen(function (DocumentCreated $event) {
// ...
});
Tratamento de Erros
O pacote fornece excepcoes tipadas para cada cenario de erro, todas a estender MoloniException.
| Excepcao | Cenario |
|---|---|
| AuthenticationException | Erros OAuth2 (credenciais invalidas, token expirado) |
| ValidationException | Falhas de validacao de dados |
| RateLimitException | Rate limit da API atingido (HTTP 429) |
| MoloniException | Todos os outros erros da API |
use DigitaldevLx\LaravelMoloni\Exceptions\MoloniException;
use DigitaldevLx\LaravelMoloni\Exceptions\AuthenticationException;
use DigitaldevLx\LaravelMoloni\Exceptions\ValidationException;
use DigitaldevLx\LaravelMoloni\Exceptions\RateLimitException;
try {
$invoice = Moloni::invoices()->insert($companyId, $data);
} catch (AuthenticationException $e) {
// $e->authError - AuthError enum
// $e->errorDescription - Descricao do erro
Log::error('Moloni auth failed', ['error' => $e->authError]);
} catch (ValidationException $e) {
// $e->errors - Array de erros
// $e->getFieldErrors() - Erros por campo
// $e->hasFieldError('vat') - Verificar campo especifico
return back()->withErrors($e->getFieldErrors());
} catch (RateLimitException $e) {
// Retry apos espera
dispatch(fn () => retry(...))->delay(now()->addMinutes(1));
} catch (MoloniException $e) {
// Erro generico da API
Log::error('Moloni error', ['message' => $e->getMessage()]);
}
Erros de validacao
A ValidationException fornece metodos para inspecionar erros campo a campo:
try {
$customer = Moloni::customers()->insert($companyId, $data);
} catch (ValidationException $e) {
// Array completo de erros
$errors = $e->errors;
// Erros organizados por campo
$fieldErrors = $e->getFieldErrors();
// ['vat' => ['NIF invalido'], 'email' => ['Email invalido']]
// Verificar se um campo especifico falhou
if ($e->hasFieldError('vat')) {
// Tratar erro de NIF
}
}
Boas Praticas
Usar DTOs em vez de arrays
Prefira sempre DTOs tipados a arrays associativos. Os DTOs garantem type safety em compile-time, autocompletar no IDE e validacao implicita da estrutura dos dados.
Tratar todas as excepcoes
A API do Moloni pode retornar erros de autenticacao, validacao ou rate limit. Trate cada tipo de excepcao de forma adequada em vez de capturar apenas MoloniException.
Emitir documentos em background
Use jobs e queues para emitir faturas e outros documentos. Isto evita timeouts em requests HTTP e permite retries automaticos em caso de falha.
// App\Jobs\CreateInvoiceJob.php
class CreateInvoiceJob implements ShouldQueue
{
public function __construct(
private Order $order,
private DocumentDto $document,
) {}
public function handle(): void
{
$companyId = config('moloni.company_id');
Moloni::invoices()->insert($companyId, $this->document);
}
}
// Despachar o job
CreateInvoiceJob::dispatch($order, $documentDto);
Reagir a eventos
Use os eventos do pacote para manter a sua aplicacao sincronizada. Por exemplo, envie um email ao cliente quando uma fatura e criada, ou atualize o stock quando um produto e modificado.
Guardar o company_id na config
Use config('moloni.company_id') em vez de hardcoded IDs. Isto facilita a mudanca entre ambientes e empresas de teste.
Usar HasMoloniDocuments nos models
Adicione a trait HasMoloniDocuments aos models que geram documentos fiscais (encomendas, subscricoes, etc.) para manter uma relacao directa entre os registos da aplicacao e os documentos no Moloni.
Testes
O pacote utiliza Pest PHP v4 (com suporte a Laravel 13), PHPStan para analise estatica e Pint para formatacao de codigo.
# Testes
vendor/bin/pest
# Analise estatica
vendor/bin/phpstan analyse
# Formatacao de codigo
vendor/bin/pint
Mocking em testes de aplicacao
Use Moloni::fake() para simular respostas da API nos seus testes sem efectuar chamadas reais:
use DigitaldevLx\LaravelMoloni\Facades\Moloni;
it('creates an invoice for the order', function () {
Moloni::fake([
'invoices.insert' => ['document_id' => 999, 'status' => 1],
]);
$order = Order::factory()->create();
CreateInvoiceJob::dispatchSync($order);
expect($order->moloniDocuments)->toHaveCount(1);
});
Migrar de v1 para v2
Breaking changes
getPdfLink() — endpoint alterado
Em v2.0, todos os metodos getPdfLink() passaram a usar o endpoint generico documents/getPDFLink. A assinatura do metodo nao muda — apenas o endpoint interno.
// Continua a funcionar igual em v2.0
$pdfLink = Moloni::invoices()->getPdfLink($companyId, $invoiceId);
$pdfLink = Moloni::receipts()->getPdfLink($companyId, $receiptId);
DocumentType enum — correcao de valor
O valor de DocumentType::PurchaseOrder foi corrigido de singular para plural para coincidir com a API Moloni. Se guardavas o valor ->value na base de dados, actualiza os registos existentes.
// v1.x: DocumentType::PurchaseOrder->value === 'purchase_order'
// v2.0: DocumentType::PurchaseOrder->value === 'purchase_orders'
// Novo tipo adicionado
DocumentType::SupplierInvoice // Faturas de Fornecedor
Pest v4 e Laravel 13
O pacote requer agora Pest PHP v4 quando se usa Laravel 13. Se ainda estiveres em Laravel 12, Pest v3 continua compativel.
$ composer require digitaldev-lx/laravel-moloni
Pronto para automatizar a faturacao?
Consulte o repositorio no GitHub para a documentacao completa, issues e contribuicoes.