aichadigital/larabill

Professional billing & invoicing package for Laravel with UUID v7, VAT verification, tax calculation for Spain/EU/worldwide, and EU compliance

Maintainers

Package info

github.com/AichaDigital/larabill

Homepage

pkg:composer/aichadigital/larabill

Fund package maintenance!

AichaDigital

Statistics

Installs: 106

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 2

v3.1.2 2026-06-29 18:25 UTC

README

Latest Version Total Downloads Tests Code Coverage PHPStan level 8 PHP Version Laravel Version License

ℹ️ Schema upgrade policy β€” Larabill does not promise in-place schema upgrades between versions. Install fresh and seed with larabill:install (or migrate:fresh) rather than migrating an existing schema across major versions. See ADR-006.

Larabill is a professional, UUID-first billing and invoicing package for Laravel applications. It provides comprehensive VAT verification, tax calculation for Spain/EU/worldwide, and flexible invoice generation with immutability protection. The consumer app's users.id MUST be UUID v7 char(36) β€” see docs/setup-uuid.md and ADR-006.

🎯 Features

Core Functionality

  • Invoice Management: UUID-based IDs, sequential numbering, proforma invoices, immutable records
  • Tax Calculation: Spanish (IVA), Canary Islands (IGIC), Ceuta/Melilla (IPSI), EU reverse charge, worldwide
  • VAT/Tax Code Verification: Integration with AbstractAPI and APILayer for real-time validation
  • Fiscal Data Management: Company and customer fiscal configurations with temporal validity
  • PDF Generation: Built-in invoice PDF generation using DomPDF
  • EU Compliance: Full support for EU B2B reverse charge and destination VAT rules

Technical Excellence

  • String UUID v7: Ordered UUIDs for invoices and the consumer's users.id (ADR-006)
  • FixedDecimal money: Precise monetary value objects backed by base-100 integers (no floating-point errors)
  • Preflight check: larabill:install aborts cleanly when users.id is not UUID-compatible
  • Temporal Validity: Fiscal configurations with valid_from/valid_until dates
  • Invoice Immutability: Protection against modifications after issuance

πŸ“¦ Requirements

πŸš€ Installation

Via Composer

composer require aichadigital/larabill

Publish Configuration

php artisan vendor:publish --tag="larabill-config"

Run the Installer

php artisan larabill:install

This will:

  1. Publish migrations
  2. Run database migrations
  3. Seed default tax categories and rates

Manual Installation (if preferred)

# Publish migrations
php artisan vendor:publish --tag="larabill-migrations"

# Run migrations
php artisan migrate

# Seed default data
php artisan db:seed --class="AichaDigital\Larabill\Database\Seeders\TaxCategoriesSeeder"
php artisan db:seed --class="AichaDigital\Larabill\Database\Seeders\TaxRatesSeeder"

βš™οΈ Configuration

Environment Variables

Add these to your .env file:

# Tax Code Verification APIs
LARABILL_ABSTRACTAPI_KEY="your_abstractapi_key"
LARABILL_APILAYER_KEY="your_apilayer_key"
LARABILL_VAT_PREFERRED_API="abstractapi"
LARABILL_VAT_CACHE_DAYS=30

# Invoice Numbering
LARABILL_INVOICE_PREFIX="FAC"
LARABILL_PROFORMA_PREFIX="PRO"

# Optional: override the User model class. Must use UUID v7 char(36) ids.
LARABILL_USER_MODEL="App\\Models\\User"

Model Configuration

Configure your user model in config/larabill.php:

'models' => [
    'user' => \App\Models\User::class,
    'invoice' => \AichaDigital\Larabill\Models\Invoice::class,
    'invoice_item' => \AichaDigital\Larabill\Models\InvoiceItem::class,
    // ...
],

πŸ—οΈ Architecture

Fiscal Data Model

Larabill separates company and customer fiscal data with temporal validity:

CompanyFiscalConfig    β†’ Issuer fiscal settings (one active at a time)
UserTaxProfile         β†’ Customer fiscal data, temporally versioned per user
Invoice                β†’ Immutable invoice with fiscal snapshot

Key principles:

  • The customer is a User (ADR-003); businesses and sub-accounts are modelled with parent_user_id. The legacy CustomerFiscalData model was removed.
  • Company config changes apply from a specific date forward
  • UserTaxProfile records are temporally versioned (valid_from/valid_until) β€” never modify past records
  • Invoices capture a fiscal snapshot at creation time
  • Invoices are absolutely immutable once issued

UUID Strategy

Larabill uses string UUID v7 for invoices:

// Model with UUID
use AichaDigital\Larabill\Concerns\HasUuid;

class Invoice extends Model
{
    use HasUuid;
}

// Migration
$table->uuid('id')->primary();

Monetary Values (FixedDecimal)

Money is stored as base-100 integers and exposed as FixedDecimal value objects (from lara100), so there are no floating-point errors. You assign the unscaled base-100 integer; reading the attribute returns a FixedDecimal:

// Assign the base-100 integer (€12.34 β†’ 1234):
$invoice->total_amount = 1234;

// Reading the attribute returns a FixedDecimal value object
// (base-100 backed, scale 2) β€” not a raw int:
$money = $invoice->total_amount; // FixedDecimal

Invoice and invoice-item money attributes use the FixedDecimalCast (scale 2) from the lara100 package. Note: query-builder access (->value(), ->sum(), ->where()) returns the raw integer, while Eloquent attribute access returns a FixedDecimal.

πŸ“– Usage

Creating an Invoice

use AichaDigital\Larabill\Services\BillingService;

$billingService = app(BillingService::class);

$invoice = $billingService->createInvoice([
    'user_id' => $user->id, // UUID v7 of the customer (a User; ADR-003)
    'items' => [
        [
            'description'  => 'Professional Service',
            'quantity'     => 100,           // base-100: 100 = 1.0 unit
            'unit_price'   => 10000,         // base-100: 10000 = €100.00
            'tax_group_id' => $taxGroup->id, // resolves the applicable VAT/IGIC/IPSI
        ],
    ],
]);

Tax Calculation

use AichaDigital\Larabill\Services\TaxCalculationService;

$taxService = app(TaxCalculationService::class);

// Calculate taxes for a single line. Amounts are base-100 integers. The
// applicable rate (Spanish IVA, Canary IGIC, Ceuta/Melilla IPSI, EU reverse
// charge or destination VAT) is resolved from the TaxGroup and the customer's
// fiscal profile β€” not passed in directly.
$result = $taxService->calculateForInvoiceItem([
    'quantity'         => 100,           // base-100: 1.0 unit
    'base_price'       => 10000,         // base-100: €100.00
    'tax_group_id'     => $taxGroup->id,
    'billable_user_id' => $user->id,     // optional: drives B2B / destination rules
]);

// $result keys (base-100 integers + breakdown):
//   taxable_amount, total_tax_amount, total_amount, tax_group_id, taxes_applied

VAT Verification

use AichaDigital\Larabill\Services\VatVerificationService;

$vatService = app(VatVerificationService::class);

$result = $vatService->verifyVatNumber('ESB12345678', 'ES');

if ($result['is_valid']) {
    echo "Valid VAT for: " . $result['company_name'];
}

Company Fiscal Configuration

use AichaDigital\Larabill\Models\CompanyFiscalConfig;

// Get current active config
$config = CompanyFiscalConfig::getActive();

// Create new config (the previous active one is auto-closed)
$newConfig = CompanyFiscalConfig::createNew([
    'tax_id'        => 'ESB12345678',
    'business_name' => 'Your Company S.L.',
    'address'       => 'Calle Test 123',
    'city'          => 'Madrid',
    'zip_code'      => '28001',
    'country_code'  => 'ES',
    'is_oss'        => true,
    'valid_from'    => now(),
]);

User Tax Profile

The customer is a User (ADR-003); their fiscal data lives in UserTaxProfile, temporally versioned. This replaces the removed CustomerFiscalData model.

use AichaDigital\Larabill\Models\UserTaxProfile;

// Get the active fiscal profile for a user
$profile = UserTaxProfile::getActiveForOwner($user->id);

// Create a new profile (previous stays as history)
$newProfile = UserTaxProfile::createForOwner($user->id, [
    'fiscal_name'  => 'Client SARL',
    'tax_id'       => 'FR12345678901',
    'country_code' => 'FR',
    'is_company'   => true,
]);

πŸ§ͺ Testing

# Run all tests
composer test

# Run specific tests
composer test -- --filter=Invoice

# Run with coverage
composer test-coverage

# Static analysis
vendor/bin/phpstan analyse

Current status (v3.1.1): 1021 tests passing on SQLite, plus MySQL 8 integration tests (real column types and unique constraints) and fork-based concurrency tests. The UUID-first contract is demonstrated on MySQL 8.

πŸ“š Documentation

Document Description
ARCHITECTURE.md Core architecture and domain model
setup-uuid.md UUID-first onboarding for the consumer app
ADR-006 UUID-first decision (supersedes the agnostic id contract)
TAX_RATES_MIGRATION_GUIDE.md Tax rates migration guide
CHANGELOG.md Version history and breaking changes

For AI agents working with this package, see .claude/project.md.

πŸ—ΊοΈ Roadmap

Shipped

  • βœ… Core invoice management (immutable records, UUID v7, sequential numbering, proforma)
  • βœ… Spanish tax system (IVA, IGIC, IPSI)
  • βœ… EU reverse charge (B2B) and destination VAT
  • βœ… Fiscal data with temporal validity (CompanyFiscalConfig, UserTaxProfile)
  • βœ… FixedDecimal money type (base-100, no floating-point errors)
  • βœ… VeriFACTU integration (Spain AEAT) via lara-verifactu
  • βœ… Grouped payments
  • βœ… Legal-retention contract (LegallyRetainable) for GDPR tooling

Under consideration

  • Subscription billing
  • Payment gateway integration (Stripe, PayPal, Redsys)
  • Advanced reporting

See the CHANGELOG for the full release history.

🀝 Contributing

Please see CONTRIBUTING for details.

πŸ”’ Security

Please review our security policy on how to report security vulnerabilities.

πŸ“„ License

GNU Affero General Public License v3.0 (AGPL-3.0-or-later). See LICENSE.md for details.

This means:

  • βœ… You can use, modify, and distribute this software
  • βœ… You must share any modifications under the same license
  • ⚠️ If you run this as a network service, you must provide the source code to users
  • ⚠️ You must preserve copyright and attribution notices

πŸ‘₯ Credits