itmosoft / laravel-pricing
A reusable Laravel pricing package with polymorphic prices, currency support, scheduled pricing, quantity breaks, and contextual price resolution.
Requires
- php: ^8.3
- illuminate/database: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A reusable Laravel pricing package for polymorphic model pricing, multiple currencies, scheduled prices, quantity breaks, and context-specific price resolution.
Requirements
- PHP 8.3+
- Laravel 12 or 13
Installation
composer require itmosoft/laravel-pricing php artisan vendor:publish --tag=pricing-config php artisan migrate
Attach the price relationship
Add a morph-many relationship to any Eloquent model that needs pricing:
use Illuminate\Database\Eloquent\Relations\MorphMany; use ItmoSoft\Pricing\Models\Price; public function prices(): MorphMany { return $this->morphMany(Price::class, 'priceable'); }
Create prices
$product->prices()->create([ 'currency' => 'USD', 'amount' => 99.00, 'compare_at_amount' => 119.00, 'min_quantity' => 1, 'is_active' => true, ]); // Quantity break $product->prices()->create([ 'currency' => 'USD', 'amount' => 85.00, 'min_quantity' => 10, 'is_active' => true, ]); // Context-specific price $product->prices()->create([ 'currency' => 'USD', 'amount' => 75.00, 'context' => ['customer_group' => 'wholesale'], 'is_active' => true, ]);
Resolve a price
use ItmoSoft\Pricing\Services\PriceResolver; $price = app(PriceResolver::class)->resolve( priceable: $product, currency: 'USD', quantity: 12, context: ['customer_group' => 'wholesale'], ); if ($price !== null) { $price->amount; $price->compare_at_amount; }
Resolution rules:
- Match the model, currency, active state, quantity threshold, and current schedule.
- A price's
contextmust be a subset of the supplied context with exact key/value matches. - The most context-specific matching price wins.
- Ties are resolved by higher
priority, then newest record. - Returns
nullwhen no price applies.
The caller is responsible for supplying trusted context values and handling a missing price.
Scheduled pricing
starts_at and ends_at are inclusive boundaries. Null means no boundary.
$product->prices()->create([ 'currency' => 'USD', 'amount' => 79.00, 'starts_at' => now()->startOfDay(), 'ends_at' => now()->addDays(7)->endOfDay(), 'is_active' => true, ]);
Configuration
Publish config/pricing.php:
php artisan vendor:publish --tag=pricing-config
table_prefix: defaultpricing_; configure before running migrations.default_currency: defaultUSD.models.price: price model class mapping.
The default table is pricing_prices.
Design boundaries
This package provides price storage and deterministic price resolution. It does not implement tax calculation, currency conversion, coupon codes, promotion campaigns, cart totals, checkout, payment processing, or automatic scheduled activation jobs. Store monetary amounts in the chosen currency; this package does not perform FX conversion.
Testing
composer install
composer validate --no-check-publish
composer test
GitHub Actions tests Laravel 12 and 13 separately using the matching Orchestra Testbench major version.
License
MIT. See LICENSE.