safemood/discountify
Laravel package for dynamic, condition-based discounts. Define custom discount rules, apply percentage discounts, set global discount and tax rate, support dynamic field names, class-based and coupon discounts, optional event tracking, and the ability to skip conditions.
Install the package via Composer:
composer require safemood/discountify
Publish the config file to customize field mappings and behavior:
php artisan vendor:publish --tag="discountify-config"
First use case: Define and apply a simple conditional discount in AppServiceProvider::boot():
use Safemood\Discountify\Facades\Condition;
use Safemood\Discountify\Facades\Discountify;
Condition::define(
'large_order_10_percent',
fn (array $items) => collect($items)->sum('quantity') > 5,
10
);
$total = Discountify::setItems($items)
->total(); // Automatically applies the condition if met
Explore documentation: Start with the Usage section of the README — especially Define Conditions, Calculate Total Amounts, and Class-Based Discounts.
Hybrid condition definitions: Combine callable-based and class-based conditions depending on complexity:
Condition::define() for simple, inline logic.discountify:condition Artisan command + classes (php artisan discountify:condition ShippingThreshold) for reusable, testable logic.Field abstraction: When working with legacy or third-party item structures, use dynamic field mapping:
Discountify::setFields(['price' => 'unit_price', 'quantity' => 'qty'])
->setItems($items)
->total();
Layered discount application:
setGlobalDiscount()Condition::define()applyCoupon($code)
Example workflow:$total = Discountify::setItems($items)
->setGlobalDiscount(5) // e.g., seasonal site-wide sale
->applyCoupon('WELCOME10') // user coupon
->total();
Extensibility via events: Register listeners to tie discount actions to business logic:
DiscountAppliedEvent: Update analytics or trigger loyalty pointsCouponAppliedEvent: Log redemption attempts or enforce usage limits via RedisState management: Configure state_file_path for persistent coupon tracking (e.g., single-use or usage-limited coupons), especially useful in CLI or headless environments where sessions don’t exist.
Breaking change in v1.0+: define() and defineIf() now require a slug. Existing code without slugs will break — always provide it:
// ❌ Invalid after v1.0
Condition::define('condition_name', $callback, 10);
// ✅ Valid
Condition::define('slug-unique', $callback, 10);
skip field behavior: Set $skip = true on class-based conditions or pass 'skip' => true in Condition::add() — otherwise conditions are always evaluated, even if the callback returns false.
Field name mismatches: Default field names are 'price' and 'quantity'. If your items use 'amount' or 'qty', either update config/discountify.php or call setFields() per request — mixing defaults and custom fields causes silent calculation errors.
Discount precedence: The package applies all non-skipped conditions but does not prioritize or chain discounts additively — it uses the highest single discount among matching conditions. Double-check with totalDetailed() to verify which condition(s) applied.
Tax calculations: calculateTaxAmount(19, true) applies tax after discounts — ensure tax logic aligns with your legal/financial requirements. The default tax() method applies tax on the pre-discount subtotal.
Coupon persistence: By default, coupons are stored in a JSON file. For production, override the state_file_path to a database-backed driver (e.g., Redis or DB table) — especially for high-traffic apps or multi-server deployments where storage/app/discountify/coupons.json won’t be shared.
Debugging tip: Always use totalDetailed() during development to verify discount logic, applied rates, savings, and tax breakdown in one go.
How can I help you explore Laravel packages today?