Download the PHP package binafy/laravel-discount without Composer

On this page you can find all versions of the php package binafy/laravel-discount. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-discount

Laravel Discount

laravel-discount

PHP Version Require Latest Stable Version Total Downloads License Passed Tests Ask DeepWiki

The Laravel-Discount is a Laravel package designed to handle discounts in your application effortlessly. This package provides a comprehensive and flexible solution to apply various discount strategies, making it easy to integrate promotional offers, seasonal sales, and other discount-related functionalities into your Laravel project.

Features

Table of Contents

Requirements

Installation

Install the package with Composer:

The service provider is registered automatically. Run the migrations to create the discounts, discount_usages, and discountables tables:

Publish Config & Migrations

Publishing is optional — the package works out of the box. Publish the config to customize table names, the user model, or code generation defaults:

Publish the migrations if you want to change the table structure before migrating:

Once a migration is published, the package no longer loads its own copy of it, so php artisan migrate runs each migration exactly once.

Upgrading an Existing Installation

Fresh installs need nothing here. If you already migrated the discounts table before the buy_x_get_y, tiered, and free_shipping types existed, the type enum still rejects them. Widen it — and make value optional, since the new types do not use it — with your own migration:

On Laravel 10 and below, ->change() requires doctrine/dbal.

The currency column arrives as a migration of its own, so php artisan migrate adds it. If you published the migrations, publish them again first to pick up the new file.

Usage

Create a Discount

Binafy\LaravelDiscount\Models\Discount is a regular Eloquent model.

Percentage Discount

Fixed Amount Discount

A fixed discount never exceeds the amount it is applied to, so the payable amount can never go below zero.

Maximum Discount Amount

Cap how much a discount can deduct — "20% off, up to 100":

Currency

A fixed amount means nothing without its currency: 10 off is a gift on a 50 EUR order and a rounding error on a 500,000 IRR one. Give a discount a currency and it only applies to orders in that currency. The currency covers every amount on the discount — a fixed value, max_discount_amount and min_order_value alike:

Tell the manager which currency the order is in through the payload:

A store that sells in one currency can say so once, in the config file or .env, and skip the payload:

A discount without a currency fits every order, which is the default — a store that never sells abroad can ignore this section entirely. The reverse is strict on purpose: a discount priced in EUR is refused when the order's currency is not known at all, rather than guessed. Nothing is converted; to run the same promotion in two currencies, create one discount per currency.

To list the discounts a customer could use in their currency, including the currency-free ones:

Buy X Get Y

"Buy 2, get 1 free" — the deal lives in the conditions column, and the manager works out how many items come free:

Because the deal counts items, pass the quantity the amount covers:

The free items are priced at the basket's average unit price, so a mixed basket is handled too. Two optional conditions refine the deal:

Condition Meaning Default
buy Items that must be paid for (required) —
get Items that come free per set (required) —
get_discount_percentage How much off the free items, e.g. 50 for "the third at half off" 100
max_free_items Cap on free items per order none

The quantity argument is also accepted by applyCode(), applyMany() and the applyDiscounts() trait method. Every other discount type ignores it.

Tiered Discount

Grow the discount with the order total. The ladder lives in the conditions column, and the highest tier the amount reaches wins:

Tiers may be listed in any order, and a tier applies exactly at its min. A tier discounts by percentage unless it sets 'type' => 'fixed':

To show the customer which tier they landed on:

Free Shipping

A free shipping discount deducts nothing from the order total. Instead it raises a flag on the result, which you honour when charging for shipping:

Free shipping sits outside the stacking competition described in Stackable Discounts: since it saves nothing on the amount, it would always lose. Every valid free shipping discount is applied on top of whichever monetary discount wins:

Everything else still applies, so min_order_value gates free shipping the usual way:

Apply a Discount

Use the LaravelDiscount facade to apply a discount to an amount. It validates the discount first and returns a DiscountResult:

To check a discount without throwing exceptions:

Discount Codes

A discount with a code acts as a coupon; a discount without one is an automatic discount.

Generate Codes

Generate cryptographically random, unique codes (ambiguous characters like 0/O and 1/I are excluded by default):

Customize the length, character set, prefix, and separator in config/laravel-discount.php under the codes key.

Apply by Code

If the code does not exist, a DiscountNotFoundException is thrown. You can also look a discount up yourself:

Expiry Dates & Time Windows

Give a discount a start date, an expiry date, or both to create time-limited offers:

Usage Limits

Limit how many times a discount can be used — in total and per user:

Redeeming

When an order is finalized, record the redemption. This creates a DiscountUsage row and increments the used_count counter atomically — the limit check happens inside the update query, so concurrent requests can never exceed the limit:

When the limit is exhausted, DiscountUsageLimitReachedException is thrown.

Guest Discounts

Guests (not-logged-in visitors) can use discounts too. Pass a session id instead of a user, and the per-user limit is enforced per session:

The discount_usages.user_id column is nullable — guest redemptions store the session_id instead.

Conditional Discounts

Minimum Order Value

The conditions JSON column also configures the Condition Engine.

Attach Discounts to Models

Add the HasDiscounts trait to any model (products, categories, ...) to make it discountable:

Condition Engine

The conditions column is not just storage — the rules you put in conditions.rules are evaluated on every validate(), isValid() and apply*() call. A discount whose rules are not met is invalid, exactly like an expired one:

Conditions read an order through a DiscountContext, so pass what they need. The quantity argument feeds item-count rules, and the payload argument carries everything else — most often the order's items:

The Laravel Cart integration fills both in for you.

The exception names the rule that failed, so you can tell the customer why:

To inspect the rules without applying anything — to render them on the coupon, say:

Bundled Conditions

Alias Class Passes when
category CategoryCondition The order's items belong to the given categories
first_purchase FirstPurchaseCondition The user has no purchase on record yet
minimum_item_count MinimumItemCountCondition The order carries at least the given number of items

Category — limits a discount to certain categories:

It reads each item's category_id; change that globally in the config file, or per rule with 'attribute' => 'category.id' (dot notation follows relations). An order with no items never matches, since there is nothing to check.

First purchase — the rule behind every WELCOME10. The package cannot know what your shop counts as a purchase, so you tell it. The quick way is to name the model that records one:

That counts every row, which is rarely the whole story — an abandoned order is not a purchase, and the number may not even live in your database. So counting can be handed back to the application with a callback. It receives the DiscountContext and returns how many purchases the user has made:

A closure cannot survive config:cache, so the config file takes an invokable class instead — as does a single rule, when one discount counts differently from the rest:

A rule's count_using wins over the config file's, which wins over countUsing(), which wins over the model query. Without any of them the rule throws InvalidDiscountConditionsException rather than quietly letting everyone through.

Guests never pass the model query: with no user to look up, a first purchase cannot be proven. A callback decides for itself — it can recognise a returning guest by the e-mail or session you put in the payload.

Minimum item count — counts the quantity handed to the manager:

Combining Rules

Every rule must pass by default. Set rules_match to any when one is enough:

Rules sit alongside the configuration of Tiered discounts, so the two can be combined freely:

Writing Your Own Condition

Implement DiscountCondition. fromArray() rebuilds the condition from its stored JSON, passes() decides, and message() explains a refusal to the customer:

Register an alias so it can be stored by name:

An alias is optional — a fully qualified class name works as the type too. Anything else is rejected with InvalidDiscountConditionsException.

Inside passes(), the context gives you the whole picture:

Stackable Discounts

Mark a discount with is_stackable => true to allow it to combine with other stackable discounts. When you apply multiple discounts, the package resolves stacking automatically:

Form Request Validation

Validate a submitted coupon code with the ValidDiscountCode rule. It checks that the code exists and is currently applicable, and the error message states the exact reason (not found, expired, usage limit reached, below minimum order, ...):

For guests, pass a session id instead of a user:

A discount with conditions is only judged fairly if the rule can see the order, so hand it the same quantity and payload you would hand applyCode() — otherwise a cart of five items looks like a cart of one and the code is rejected for no reason:

The message is the failing condition's own, so the customer reads "This discount requires at least 2 items." rather than a generic refusal.

Validation & Exceptions

Every failure case has its own exception, all extending Binafy\LaravelDiscount\Exceptions\DiscountException:

Exception Thrown when
DiscountNotFoundException The given code does not exist
DiscountNotActiveException The discount is disabled (is_active = false)
DiscountNotStartedException starts_at is in the future
DiscountExpiredException expires_at is in the past
DiscountUsageLimitReachedException The total or per-user usage limit is reached
MinimumOrderValueException The order total is below min_order_value
InvalidDiscountConditionsException A "buy X get Y", tiered, or condition rule is misconfigured
DiscountConditionFailedException The order does not meet the discount's conditions
DiscountCurrencyMismatchException The order is in another currency than the discount, or its currency is unknown

Each exception carries the discount that failed, so you can handle every case separately:

Events

Event Dispatched when
DiscountApplied One or more discounts are applied to an amount
DiscountRedeemed A redemption is recorded (after the transaction commits)
DiscountExpired Validation encounters an expired discount

Laravel Cart Integration

If binafy/laravel-cart is installed, the CartDiscount service becomes available:

The cart total is checked against min_order_value, and the cart's user is used for per-user usage limits automatically.

Item quantities and items are collected for you, so Condition Engine work without passing anything extra: applyToCart() counts every unit in the cart and hands over every item model, while applyToItem() and applyItemDiscounts() scope both to the item at hand. Free shipping attached to any single item makes the whole order's shipping free:

Artisan Commands

Generate unique discount codes from the command line:

Delete expired discounts (their usage records are removed with them):

discount:prune works well as a scheduled task:

Testing

Contributors

Thanks to all the people who contributed. Contributors.

Security

If you discover any security-related issues, please email [email protected] instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.


All versions of laravel-discount with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
laravel/framework Version ^9.0|^10.0|^11.0|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package binafy/laravel-discount contains the following files

Loading the files please wait ...