bkfdev/invoicable
Invoicable adds flexible invoicing to Laravel apps, letting you generate invoices from Eloquent models with line items, taxes, totals, and status tracking. A lightweight, developer-friendly package for billing workflows.
composer require <vendor>/<package>:^1.1
php artisan vendor:publish --tag=<package>-config
php artisan vendor:publish --tag=<package>-migrations
store_id column added to payments in v1.1.8:
php artisan migrate
config/<package>.php for default store/tenant behavior and model bindings.payments migration for the store_id foreign key, nullability, and indexing.Payment model for $fillable, relationships, and scopes.Store model for the inverse payments() relationship.$payment = $store->payments()->create([
'amount' => 2500,
'currency' => 'USD',
// other package-required fields
]);
$store->payments()->latest()->get();
store_id consistentlySince v1.1.8, payments carry a store_id. Treat it as required application context, even if the database column is nullable for backward compatibility.
$payment = Payment::create([
'store_id' => $store->id,
'amount' => $amount,
'currency' => $currency,
]);
Prefer creating through the relationship where possible:
$store->payments()->create($payload);
Use the relationship or explicit scoping for all store-facing reads:
$payments = $store->payments()
->where('status', 'succeeded')
->latest()
->paginate();
For dashboards or reporting, group by store:
Payment::query()
->selectRaw('store_id, SUM(amount) as total')
->groupBy('store_id')
->with('store')
->get();
Validate that the submitted store_id belongs to the authenticated user/tenant, not just that it exists:
$data = $request->validate([
'store_id' => ['required', 'integer', 'exists:stores,id'],
'amount' => ['required', 'integer', 'min:1'],
]);
abort_unless(
$request->user()->stores()->whereKey($data['store_id'])->exists(),
403
);
If your application uses a current-store context, resolve store_id server-side rather than trusting request input.
Every payment creation path must set store_id. Audit:
Payment::factory()->create([
'store_id' => Store::factory(),
]);
Expose store_id deliberately. If clients only need the store relationship, consider exposing a nested identifier instead:
return [
'id' => $this->id,
'store_id' => $this->store_id,
'amount' => $this->amount,
'currency' => $this->currency,
];
Eager load store when list endpoints include store details to avoid N+1 queries.
For existing installations, migrate in stages:
store_id.store_id.Keep the backfill idempotent and batched.
store_id: v1.1.8 may add the column as nullable to avoid breaking existing rows. Do not assume it is always populated. Add application-level validation and a backfill plan.store_id, leaving orphaned or unscoped payments. Search the codebase for Payment::create, payments()->create, updateOrCreate, and raw inserts.store_id only in the UI is insufficient. Enforce store ownership in policies, form requests, or query scopes.cascadeOnDelete, restrictOnDelete, or no constraint. Deleting a store should not silently delete financial records unless that is explicitly intended.store_id is in the package model’s $fillable or assign it explicitly. If the package guards it, use forceFill() carefully or the relationship.with('store').store_id values unexpectedly.php artisan migrate:status, inspect the payments table schema, log the raw SQL via DB::listen(), and verify $payment->store resolves correctly.Payment model bound in config over editing vendor migrations. For multi-tenant apps, a global scope keyed to the current store may be appropriate, but ensure background jobs explicitly set the store context.How can I help you explore Laravel packages today?