PHP code example of salehye / invoicing

1. Go to this page and download the library: Download salehye/invoicing library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

salehye / invoicing example snippets


use Salehye\Invoicing\Traits\HasInvoices;

class Customer extends Model
{
    use HasInvoices;
}

use Salehye\Invoicing\Facades\Invoicing;

$invoice = Invoicing::create([
    'billable'  => $customer,
    'title'     => 'Order #123',
    'currency'  => 'SAR',
    'due_at'    => now()->addDays(14),
    'user_id'   => auth()->id(),       // optional: track who created it
    'items'     => [
        ['description' => 'Product A', 'quantity' => 2, 'unit_price' => 150],
        ['description' => 'Product B', 'quantity' => 1, 'unit_price' => 300],
    ],
]);

// $invoice->subtotal = 600 (150*2 + 300)
// $invoice->total   = 600
// $invoice->status  = Draft
// $invoice->number  = "INV-2025-0001"

Invoicing::markAsIssued($invoice);
// draft → unpaid, issued_at is set

use Salehye\Invoicing\Services\PaymentProcessor;

$processor = app(PaymentProcessor::class);

// Record a payment
$payment = $processor->recordPayment($invoice, 'manual', 600.00);
$processor->markAsSuccess($payment);

// Invoice automatically marked as paid!
$invoice->isPaid(); // true

// With billable entity
$invoice = Invoicing::create([
    'billable'  => $customer,
    'title'     => 'Service Invoice',
    'currency'  => 'SAR',
    'due_at'    => now()->addDays(30),
    'items'     => [
        ['description' => 'Web Development', 'quantity' => 1, 'unit_price' => 5000],
        ['description' => 'Hosting (12 months)', 'quantity' => 1, 'unit_price' => 1200],
    ],
]);

// Standalone (no billable)
$invoice = Invoicing::create([
    'title'     => 'Walk-in Sale',
    'currency'  => 'USD',
    'items'     => [
        ['description' => 'Coffee', 'quantity' => 3, 'unit_price' => 5],
    ],
]);

// With tenant ID (multi-tenant)
$invoice = Invoicing::create([
    'billable'  => $customer,
    'title'     => 'Tenant Invoice',
    'tenant_id' => 'tenant-123',
    'items'     => [...],
]);

// Percentage discount (10% off)
$invoice = Invoicing::create([
    'billable'       => $customer,
    'title'          => 'Discounted Service',
    'items'          => [
        ['description' => 'Consultation', 'quantity' => 1, 'unit_price' => 1000],
    ],
    'discount'       => 10,
    'discount_type'  => 'percentage',  // DiscountType enum: 'percentage' or 'fixed'
    'tax'            => 15,             // 15% VAT
]);

// Calculation:
// Subtotal = 1000
// Discount (10%) = -100 → After discount = 900
// Tax (15% of 900) = +135
// Total = 1035

// Fixed discount (50 SAR off)
$invoice = Invoicing::create([
    'billable'       => $customer,
    'title'          => 'Fixed Discount',
    'items'          => [...],
    'discount'       => 50,
    'discount_type'  => 'fixed',
    'tax'            => 15,
]);

// Calculation:
// Subtotal = 1000, Discount = -50 → 950
// Tax (15% of 950) = +142.50
// Total = 1092.50

$invoice = Invoicing::create([
    'billable'  => $customer,
    'title'     => 'Order Invoice',
    'items'     => [...],
    'metadata'  => [
        'order_id'     => $order->id,
        'source'       => 'web_checkout',
        'coupon_code'  => 'SAVE20',
    ],
]);

// Access later
$invoice->metadata['order_id'];

$invoice = Invoicing::create([
    'billable'  => $customer,
    'title'     => 'Mixed Invoice',
    'items'     => [
        [
            'description' => 'Premium Service',
            'quantity'    => 1,
            'unit_price'  => 500,
            'discount'    => 50,       // per-line discount
            'tax'         => 75,       // per-line tax
        ],
    ],
]);
// Line total = (500*1) - 50 + 75 = 525

Invoicing::addLine($invoice, [
    'description' => 'Late Fee',
    'quantity'    => 1,
    'unit_price'  => 50,
]);

// Recalculate totals after adding/removing lines
Invoicing::recalculateTotals($invoice);

'invoice_number_format' => '{prefix}-{year}-{sequence}',
'invoice_number_prefix' => 'INV',
'invoice_number_sequence_length' => 4,

// Issue: draft → unpaid
Invoicing::markAsIssued($invoice);

// Pay: unpaid → paid
Invoicing::markAsPaid($invoice);

// Cancel: draft/unpaid → canceled
Invoicing::cancel($invoice);

// Refund: paid → refunded
Invoicing::refund($invoice);

// Check if overdue (Unpaid + past due_at, or Overdue status)
$invoice->isOverdue(); // bool

// Mark as overdue (via command)
php artisan invoicing:mark-overdue

// Overdue invoices can transition to: Paid or Canceled
$invoice->status->canTransitionTo(InvoiceStatus::Paid);    // true
$invoice->status->canTransitionTo(InvoiceStatus::Canceled); // true

'overdue_threshold_days' => 0,  // 0 = immediately overdue after due_at
'overdue_threshold_days' => 3,  // 3 days grace period

$schedule->command('invoicing:mark-overdue')->dailyAt('08:00');

$invoice->isDraft();      // status === Draft
$invoice->isUnpaid();     // status === Unpaid
$invoice->isPaid();       // status === Paid
$invoice->isCanceled();   // status === Canceled
$invoice->isRefunded();   // status === Refunded
$invoice->isOverdue();    // status === Overdue OR (Unpaid + past due_at)

$invoice->totalPaid();         // sum of successful payments
$invoice->remainingBalance();   // total - totalPaid (min 0)
$invoice->isFullyPaid();        // true if remainingBalance <= 0
$invoice->hasLines();           // true if invoice has line items
$invoice->lineCount();          // number of line items
$invoice->lines;                // HasMany relationship
$invoice->payments;             // HasMany relationship
$invoice->billable;             // MorphTo relationship (nullable)
$invoice->user;                 // BelongsTo User (nullable)

Invoice::forTenant('tenant-1')->get();      // filter by tenant
Invoice::forUser(1)->get();                  // filter by user
Invoice::status(InvoiceStatus::Paid)->get(); // filter by status

$processor = app(PaymentProcessor::class);

$payment = $processor->recordPayment(
    invoice:        $invoice,
    gateway:        'manual',
    amount:         500.00,
    transactionId:  'TXN-001',
    userId:         auth()->id(),  // optional
);

// Amount validation: must be > 0 and ≤ remainingBalance()
// Throws InvalidPaymentAmountException on violation

$processor->markAsSuccess($payment);
// Fires PaymentSucceeded event
// Auto-marks invoice as paid if fully paid

$processor->markAsFailed($payment);
// Fires PaymentFailed event

$checkout = $processor->createCheckout(
    invoice:   $invoice,
    returnUrl: 'https://example.com/success',
    cancelUrl: 'https://example.com/cancel',
    gateway:   'stripe',  // optional, defaults to config
);

$webhookEvent = $processor->handleWebhook($payload, 'stripe');

$processor->refund($invoice, null, 'stripe');
// Returns bool

$payment->isPending();
$payment->isAwaitingVerification();
$payment->isSuccess();
$payment->isFailed();
$payment->isRefunded();
$payment->needsVerification();
$payment->invoice;      // BelongsTo Invoice
$payment->user;         // BelongsTo User (nullable)
$payment->verifier;     // BelongsTo User via verified_by (nullable)

// config/invoicing.php
'default_gateway' => 'stripe',

$processor->createCheckout($invoice, '/success', '/cancel', 'stripe');
$processor->refund($invoice, null, 'stripe');

'gateways' => [
    'paypal' => [
        'driver' => \App\Gateways\PayPalGateway::class,
        'api_key' => env('PAYPAL_API_KEY'),
    ],
],

use Salehye\Invoicing\Services\GatewayManager;

app(GatewayManager::class)->register('paypal', \App\Gateways\PayPalGateway::class);

use Salehye\Invoicing\Contracts\PaymentGateway;
use Salehye\Invoicing\Contracts\WebhookEvent;
use Salehye\Invoicing\Models\Invoice;

class PayPalGateway implements PaymentGateway
{
    public function createCheckout(Invoice $invoice, string $returnUrl, string $cancelUrl): array
    {
        return ['checkout_url' => 'https://paypal.com/pay/...'];
    }

    public function handleWebhook(array $payload): ?WebhookEvent
    {
        return null;
    }

    public function getPaymentStatus(string $transactionId): string
    {
        return 'success';
    }

    public function refund(Invoice $invoice, ?float $amount = null): bool
    {
        return true;
    }
}

$manager = app(GatewayManager::class);

$manager->names();      // ['local', 'stripe', 'bank_transfer', ...]
$manager->has('paypal'); // bool
$manager->gateway();     // default gateway instance
$manager->gateway('stripe'); // specific gateway instance

// Unregistered → throws GatewayNotFoundException
$manager->gateway('unknown');

$processor = app(PaymentProcessor::class);

// Show bank details to customer
$checkout = $processor->createCheckout($invoice, '/success', '/cancel', 'bank_transfer');
// Returns: type, invoice_id, amount, currency, bank_details, reference, instructions

// Customer uploads proof
$payment = $processor->initiateBankTransfer($invoice, 'receipt.pdf', 'Paid via Al Rajhi');
// Status: awaiting_verification

// Verify — invoice auto-marked as paid if fully paid
$processor->verify($payment, auth()->id());
// verified_by = user ID (int), verified_at = now()

// Reject — with reason
$processor->reject($payment, 'Receipt is unclear');
// proof_notes updated with reason

// Wrong status → throws PaymentVerificationException

'gateways' => [
    'bank_transfer' => [
        'bank_details' => [
            'bank_name'      => 'Al Rajhi Bank',
            'account_name'   => 'My Company',
            'account_number' => '1234567890',
            'iban'           => 'SA0380000000608010167519',
            'swift_code'     => 'RJHISARI',
        ],
        'instructions' => 'Transfer the amount and upload proof of payment.',
    ],
],

PaymentStatus::Pending->canTransitionTo(PaymentStatus::Success);    // true
PaymentStatus::Pending->canTransitionTo(PaymentStatus::Failed);     // true
PaymentStatus::Success->canTransitionTo(PaymentStatus::Refunded);   // true
PaymentStatus::Failed->canTransitionTo(PaymentStatus::Success);     // false

// App\Providers\EventServiceProvider
protected $listen = [
    \Salehye\Invoicing\Events\InvoicePaid::class => [
        \App\Listeners\SendInvoicePaidNotification::class,
    ],
    \Salehye\Invoicing\Events\PaymentVerified::class => [
        \App\Listeners\NotifyCustomerPaymentVerified::class,
    ],
];

class SendInvoicePaidNotification
{
    public function handle(InvoicePaid $event): void
    {
        $invoice = $event->invoice;  // readonly — cannot be modified
        Mail::to($invoice->billable)->send(new InvoicePaidMail($invoice));
    }
}

// Just use it directly — no manual registration ller::class, 'download'])
    ->middleware('invoice.paid:invoice');

// The parameter name is configurable: 'invoice.paid:invoice_id'

// bootstrap/app.php (Laravel 11+)
$app->routeMiddleware([
    'invoice.paid' => \Salehye\Invoicing\Middleware\EnsureInvoicePaid::class,
]);

class Customer extends Model
{
    use HasInvoices;
}

// Available methods
$customer->invoices();            // MorphMany — all invoices
$customer->draftInvoices();       // MorphMany — status = Draft
$customer->unpaidInvoices();      // MorphMany — status = Unpaid
$customer->paidInvoices();        // MorphMany — status = Paid
$customer->canceledInvoices();    // MorphMany — status = Canceled
$customer->overdueInvoices();     // MorphMany — Overdue OR unpaid + past due_at
$customer->refundedinvoices();    // MorphMany — status = Refunded
$customer->totalInvoiceBalance(); // float — sum of unpaid totals
$customer->totalPaidAmount();     // float — sum of paid totals

// Invoice with user
$invoice = Invoicing::create([
    'billable' => $customer,
    'title'    => 'Order Invoice',
    'user_id'  => auth()->id(),
    'items'    => [...],
]);

$invoice->user; // BelongsTo → configured User model

// Payment with user
$payment = $processor->recordPayment($invoice, 'stripe', 100, userId: auth()->id());
$payment->user; // BelongsTo → configured User model

// Bank transfer inherits user_id from invoice
$payment = $processor->initiateBankTransfer($invoice);
// payment.user_id = invoice.user_id

// Override with explicit user
$payment = $processor->initiateBankTransfer($invoice, null, null, $otherUserId);

// config/invoicing.php
'user_model' => \App\Models\Admin::class,

use Salehye\Invoicing\Contracts\TaxCalculator;

class SaudiVatCalculator implements TaxCalculator
{
    public function calculate(float $subtotal, ?array $metadata = null): float
    {
        return round($subtotal * 0.15, 2);
    }
}

// Register in a service provider
app()->singleton(TaxCalculator::class, SaudiVatCalculator::class);

use Salehye\Invoicing\Contracts\DiscountCalculator;

class CouponDiscountCalculator implements DiscountCalculator
{
    public function calculate(float $subtotal, ?array $metadata = null): float
    {
        $coupon = $metadata['coupon'] ?? null;
        return $coupon ? $coupon->applyTo($subtotal) : 0;
    }
}

app()->singleton(DiscountCalculator::class, CouponDiscountCalculator::class);
bash
php artisan vendor:publish --tag=invoicing-config
php artisan vendor:publish --tag=invoicing-migrations
php artisan migrate