binafy/laravel-discount 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

binafy/laravel-discount

Composer 安装命令:

composer require binafy/laravel-discount

包简介

README 文档

README

laravel-discount

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

  • Percentage Discounts: Apply percentage-based discounts to your products or services.
  • Fixed Amount Discounts: Deduct a fixed amount from the total cost.
  • Conditional Discounts: Set conditions for discounts, such as minimum order value or specific product categories.
  • Discount Codes: Generate and manage discount codes for your customers.
  • Expiry Dates: Set expiration dates for discounts to create time-limited offers.
  • Usage Limits: Restrict the number of times a discount can be used.
  • Stackable Discounts: Allow multiple discounts to be applied simultaneously or restrict stacking.
  • Support Laravel Cart
  • Detailed Documentation: Comprehensive guides and examples to help you get started quickly.

Table of Contents

Requirements

  • PHP 8.0 or higher
  • Laravel 9.0 or higher

Installation

Install the package with Composer:

composer require binafy/laravel-discount

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

php artisan migrate

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:

php artisan vendor:publish --tag="laravel-discount-config"

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

php artisan vendor:publish --tag="laravel-discount-migrations"

Usage

Create a Discount

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

Percentage Discount

use Binafy\LaravelDiscount\Enums\DiscountType;
use Binafy\LaravelDiscount\Models\Discount;

$discount = Discount::query()->create([
    'name' => 'Summer Sale',
    'type' => DiscountType::Percentage,
    'value' => 20, // 20%
]);

Fixed Amount Discount

$discount = Discount::query()->create([
    'name' => 'Ten dollars off',
    'type' => DiscountType::Fixed,
    'value' => 10, // deducts 10 from the total
]);

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

Apply a Discount

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

use Binafy\LaravelDiscount\Facades\LaravelDiscount;

$result = LaravelDiscount::apply($discount, 200);

$result->originalAmount;   // 200.0
$result->discountAmount;   // 40.0
$result->payableAmount();  // 160.0
$result->discounts;        // Collection of the applied discounts

To check a discount without throwing exceptions:

LaravelDiscount::isValid($discount, orderAmount: 200, user: $user); // true|false

Discount Codes

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

$discount = Discount::query()->create([
    'code' => 'WELCOME10',
    'type' => DiscountType::Percentage,
    'value' => 10,
]);

Generate Codes

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

LaravelDiscount::generateCode();              // "8FJ2K9QW"
LaravelDiscount::generateCode('SUMMER');      // "SUMMER-8FJ2K9QW"
LaravelDiscount::generateCodes(100, 'VIP');   // Collection of 100 unique codes

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

Apply by Code

$result = LaravelDiscount::applyCode('WELCOME10', 200, $user);

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

$discount = LaravelDiscount::findByCode('WELCOME10');

Expiry Dates & Time Windows

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

$discount = Discount::query()->create([
    'code' => 'BLACK-FRIDAY',
    'type' => DiscountType::Percentage,
    'value' => 30,
    'starts_at' => now()->startOfDay(),
    'expires_at' => now()->addDays(3),
]);
  • Before starts_at, applying throws DiscountNotStartedException.
  • After expires_at, applying throws DiscountExpiredException (and dispatches the DiscountExpired event).
  • Query only the currently applicable discounts with the valid() scope:
Discount::query()->valid()->get();

Usage Limits

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

$discount = Discount::query()->create([
    'code' => 'FIRST-100',
    'type' => DiscountType::Fixed,
    'value' => 15,
    'usage_limit' => 100,        // first 100 redemptions only
    'usage_limit_per_user' => 1, // once 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:

LaravelDiscount::redeem($discount, $user, $result->discountAmount);

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:

$result = LaravelDiscount::applyCode('GUEST10', $total, sessionId: session()->getId());

LaravelDiscount::redeem($discount, amount: $result->discountAmount, sessionId: session()->getId());

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

Conditional Discounts

Minimum Order Value

$discount = Discount::query()->create([
    'code' => 'BIG-SPENDER',
    'type' => DiscountType::Percentage,
    'value' => 15,
    'min_order_value' => 500,
]);

LaravelDiscount::applyCode('BIG-SPENDER', 300); // throws MinimumOrderValueException
LaravelDiscount::applyCode('BIG-SPENDER', 800); // OK

The conditions JSON column is also available for storing your own arbitrary condition data.

Attach Discounts to Models

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

use Binafy\LaravelDiscount\Traits\HasDiscounts;

class Product extends Model
{
    use HasDiscounts;
}
// Attach and query
$product->discounts()->attach($discount);
$product->validDiscounts();       // only the currently applicable ones
$product->hasDiscount('TECH10');  // by code or by model instance

// Apply all attached valid discounts to a price (stacking rules included)
$result = $product->applyDiscounts($product->price);
$result->payableAmount();

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:

  • Stackable discounts are combined (their total never exceeds the amount).
  • Non-stackable discounts compete alone.
  • Whichever saves the customer the most wins.
  • Invalid discounts are silently skipped.
$result = LaravelDiscount::applyMany([$tenPercent, $tenFixed, $bigSolo], 100);

$result->discounts;       // the discounts that were actually applied
$result->discountAmount;  // the winning total

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

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

use Binafy\LaravelDiscount\Exceptions\DiscountException;
use Binafy\LaravelDiscount\Exceptions\DiscountExpiredException;

try {
    $result = LaravelDiscount::applyCode($code, $total, $user);
} catch (DiscountExpiredException $e) {
    return back()->withErrors("Code {$e->getDiscount()->code} has expired.");
} catch (DiscountException $e) {
    return back()->withErrors($e->getMessage());
}

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
use Binafy\LaravelDiscount\Events\DiscountRedeemed;

Event::listen(DiscountRedeemed::class, function (DiscountRedeemed $event) {
    // $event->discount, $event->usage
});

Laravel Cart Integration

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

composer require binafy/laravel-cart
use Binafy\LaravelDiscount\Integrations\LaravelCart\CartDiscount;

$cartDiscount = app(CartDiscount::class);

// Apply a code (or discount models) to the whole cart total
$result = $cartDiscount->applyToCart($cart, 'SUMMER-8FJ2K9QW');
$result->payableAmount();

// Apply a discount to a specific cart item (price × quantity)
$result = $cartDiscount->applyToItem($cartItem, $discount);

// Automatically apply the discounts attached to each item's model
// (via the HasDiscounts trait) across the whole cart
$result = $cartDiscount->applyItemDiscounts($cart);

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

Artisan Commands

Generate unique discount codes from the command line:

php artisan discount:generate                    # one code
php artisan discount:generate 100 --prefix=VIP  # 100 codes like VIP-8FJ2K9QW

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

php artisan discount:prune            # everything already expired
php artisan discount:prune --days=30  # only discounts expired 30+ days ago

discount:prune works well as a scheduled task:

Schedule::command('discount:prune --days=30')->daily();

Testing

composer install
./vendor/bin/pest

Contributors

Thanks to all the people who contributed. Contributors.

Security

If you discover any security-related issues, please email binafy23@gmail.com instead of using the issue tracker.

License

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

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 23
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 23
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-06

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固