safemood / discountify
Laravel package for dynamic discounts with custom conditions.
Fund package maintenance!
Safemood
Installs: 2 534
Dependents: 0
Suggesters: 0
Security: 0
Stars: 203
Watchers: 3
Forks: 14
Open Issues: 0
Requires
- php: ^8.1|^8.2
- illuminate/contracts: ^10|^11.1
- spatie/laravel-package-tools: ^1.14.0
Requires (Dev)
- larastan/larastan: ^2.7.0
- laravel/pint: ^1.13.7
- nunomaduro/collision: ^7.0|^8.9
- orchestra/testbench: ^8.21.1|^9.0.0
- pestphp/pest: ^2.30
- pestphp/pest-plugin-arch: ^2.5
- pestphp/pest-plugin-laravel: ^2.2
- pestphp/pest-plugin-type-coverage: ^2.8
- phpstan/extension-installer: ^1.3.1
- phpstan/phpstan-deprecation-rules: ^1.1.4
README
Discountify is a Laravel package designed for managing dynamic discounts with custom conditions. It allows you to create flexible and powerful discounting strategies, easily defining conditions and applying percentage-based discounts to enhance your e-commerce application.
Installation
You can install the package via composer:
composer require safemood/discountify
You can publish the config file with:
php artisan vendor:publish --tag="discountify-config"
This is the contents of the published config file:
// config/discountify.php return [ 'condition_namespace' => 'App\\Conditions', 'condition_path' => app_path('Conditions'), 'fields' => [ 'price' => 'price', 'quantity' => 'quantity', ], 'global_discount' => 0, 'global_tax_rate' => 0, 'fire_events' => env('DISCOUNTIFY_FIRE_EVENTS', true), 'state_file_path' => env('DISCOUNTIFY_STATE_FILE_PATH', storage_path('app/discountify/coupons.json')), ];
Usage
Define Conditions
use Illuminate\Support\ServiceProvider; use Safemood\Discountify\Facades\Condition; use Carbon\Carbon; class AppServiceProvider extends ServiceProvider { public function boot() { // If items are more than 2, apply a 10% discount. Condition::define('more_than_2_products_10', fn (array $items) => count($items) > 2, 10) // If the date is within a 7-day interval starting March 1, 2024, apply a 15% discount. ->add([ [ 'slug' => 'promo_early_spring_sale_2024', 'condition' => fn ($items) => now()->between( Carbon::createFromDate(2024, 3, 1), Carbon::createFromDate(2024, 3, 15)->addDays(7) ), 'discount' => 15, ], // If 'special' items are in the cart, apply a 10% discount. [ 'slug' => 'special_type_product_10', 'condition' => fn ($items) => in_array('special', array_column($items, 'type')), 'discount' => 10, ], ]) // If the user has a renewal, apply a 10% discount. ->defineIf('client_has_renewal_10', auth()->user()->hasRenewal(), 10); } }
Set Items, Global Discount, and Tax Rate
$items = [ ['id' => '1', 'quantity' => 2, 'price' => 50], ['id' => '2', 'quantity' => 1, 'price' => 100, 'type' => 'special'], ]; // Set the items in the cart Discountify::setItems($items) // Set a global discount for all items in the cart ->setGlobalDiscount(15) // Set a global tax rate for all items in the cart ->setGlobalTaxRate(19);
Calculate Total Amounts
// Calculate the total amount considering the set conditions and discounts $total = Discountify::total(); // Calculate total amount with detailed breakdown // (array contains total, subtotal, tax amount, total after discount, savings, tax rate, discount rate) $total = Discountify::totalDetailed(); // Calculate the total amount with the applied global discount $totalWithDiscount = Discountify::totalWithDiscount(); // Calculate the total amount with taxes applied based on the set global tax rate $totalWithTaxes = Discountify::tax(); // Calculate the total tax amount based on the given tax rate (19 in this case) (before discounts) $taxAmount = Discountify::taxAmount(19); // Calculate tax amount with tax applied after discounts $taxAmount = Discountify::calculateTaxAmount(19, true); // Calculate the amount saved $savings = Discountify::savings();
Dynamic Field Names
// Set custom field names through configuration return [ 'condition_namespace' => 'App\\Conditions', 'condition_path' => app_path('Conditions'), 'fields' => [ 'price' => 'price', 'quantity' => 'quantity', ], 'global_discount' => 0, 'global_tax_rate' => 0, 'fire_events' => env('DISCOUNTIFY_FIRE_EVENTS', true) ]; // Alternatively, set dynamic field names on the fly $items = [ ['id' => 'item1', 'qty' => 2, 'amount' => 20], ['id' => 'item2', 'qty' => 1, 'amount' => 20], ]; $discountify->setFields([ 'price' => 'amount', 'quantity' => 'qty', ])->setItems($items); $totalWithDiscount = $discountify->totalWithDiscount(50);
Class-Based Discounts
The classes in App\Conditions will be auto-discovered by Discountify for seamless integration—no configuration is needed.
- Create Class-Based Conditions:
To create a class-based condition using the discountify:condition
artisan command, you can run the following command:
php artisan discountify:condition OrderTotalDiscount
php artisan discountify:condition OrderTotalDiscount --discount=10 --slug OrderTotal
<?php namespace App\Conditions; use Safemood\Discountify\Contracts\ConditionInterface; class OrderTotalDiscount implements ConditionInterface { public bool $skip = true; // Set to true to skip the condition public string $slug = 'order_total'; public int $discount = 10; public function __invoke(array $items): bool { return count($items) > 5; } }
- Command Options:
--discount (-d): Specifies the discount value for the condition. Default value is 0.
--slug (-s): Specifies the slug for the condition. If not provided, the name of the condition will be used as the slug.
--force (-f): Creates the class even if the condition class already exists.
Skip Discounts Conditions
This will allows you to exclude specific conditions based on the "skip" field.
Using Condition::define:
Condition::define('condition2', fn ($items) => false, 20, true); // Will be skipped
- Using Condition::add:
Condition::add([ ['slug' => 'condition1', 'condition' => fn ($items) => true, 'discount' => 10, 'skip' => false], // Won't be skipped ['slug' => 'condition2', 'condition' => fn ($items) => false, 'discount' => 20, 'skip' => true], // Will be skipped ['slug' => 'condition3', 'condition' => fn ($items) => true, 'discount' => 30], // Will not be skipped (default skip is false) ]);
Event Tracking
You can listen for the DiscountAppliedEvent
and CouponAppliedEvent
using Laravel's Event system.
Ensure the following configuration in the discountify.php file:
// config/discountify.php 'fire_events' => env('DISCOUNTIFY_FIRE_EVENTS', true) // Toggle event dispatching
// app/Providers/EventServiceProvider.php use Illuminate\Support\Facades\Event; use Safemood\Discountify\Events\DiscountAppliedEvent; public function boot(): void { Event::listen(function (DiscountAppliedEvent $event) { // Your event handling logic here // Ex : Mail to customer // dd($event); }); Event::listen(function (CouponAppliedEvent $event) { // Added // Your event handling logic for CouponAppliedEvent here // Example: Log coupon usage // dd($event); }); }
Check the Laravel Events documentation for more details.
Coupon Based Discounts
Coupon based discounts to easily apply and calculate discounts (Percentage) on a given coupon code.
Discountify Coupons allows you to apply various types of coupons to your cart.
Period-Limited Coupon
use Safemood\Discountify\Facades\Coupon; Coupon::add([ 'code' => 'PERIODLIMITED50', 'discount' => 50, 'startDate' => now(), 'endDate' => now()->addWeek(), ]); $discountedTotal = Discountify::setItems($items) ->applyCoupon('TIMELIMITED50') ->total();
Single-Use Coupon
use Safemood\Discountify\Facades\Coupon; Coupon::add([ 'code' => 'SINGLEUSE50', 'discount' => 50, 'singleUse' => true, 'startDate' => now(), 'endDate' => now()->addWeek(), ]); $discountedTotal = Discountify::setItems($items) ->applyCoupon('SINGLEUSE50') ->total();
Restricted User Coupon
use Safemood\Discountify\Facades\Coupon; Coupon::add([ 'code' => 'RESTRICTED20', 'discount' => 20, 'userIds' => [123, 456], // Restricted to user IDs 123 and 456 'startDate' => now(), 'endDate' => now()->addWeek(), ]); $discountedTotal = Discountify::setItems($items) ->applyCoupon('RESTRICTED20', 123) // Applying to user ID 123 ->total();
Limited Usage Coupon
use Safemood\Discountify\Facades\Coupon; Coupon::add([ 'code' => 'LIMITED25', 'discount' => 25, 'usageLimit' => 3, // Limited to 3 uses 'startDate' => now(), 'endDate' => now()->addWeek(), ]); $discountedTotal = Discountify::setItems($items) ->applyCoupon('LIMITED25') ->total();
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.