*/ use HasFactory; use SoftDeletes; protected $fillable = [ 'name', 'code', 'type', 'amount', 'currency', 'status', 'is_stackable', 'enabled_for_checkout', 'auto_apply', 'usage_limit', 'per_customer_limit', 'redemptions_count', 'description', 'metadata', 'starts_at', 'ends_at', 'paddle_discount_id', 'paddle_mode', 'paddle_snapshot', 'paddle_last_synced_at', 'created_by', 'updated_by', ]; protected $casts = [ 'type' => CouponType::class, 'status' => CouponStatus::class, 'amount' => 'decimal:2', 'is_stackable' => 'boolean', 'enabled_for_checkout' => 'boolean', 'auto_apply' => 'boolean', 'metadata' => 'array', 'paddle_snapshot' => 'array', 'starts_at' => 'datetime', 'ends_at' => 'datetime', 'paddle_last_synced_at' => 'datetime', ]; protected static function booted(): void { static::saving(function (self $coupon): void { if ($coupon->code) { $coupon->code = Str::upper($coupon->code); } if ($coupon->currency) { $coupon->currency = Str::upper($coupon->currency); } }); } public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function updatedBy(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); } public function packages(): BelongsToMany { return $this->belongsToMany(Package::class)->withTimestamps(); } public function checkoutSessions(): HasMany { return $this->hasMany(CheckoutSession::class); } public function redemptions(): HasMany { return $this->hasMany(CouponRedemption::class); } public function giftVoucher(): HasOne { return $this->hasOne(GiftVoucher::class); } public function scopeActive($query) { return $query->where('status', CouponStatus::ACTIVE) ->where(function ($inner) { $now = now(); $inner->whereNull('starts_at')->orWhere('starts_at', '<=', $now); }) ->where(function ($inner) { $now = now(); $inner->whereNull('ends_at')->orWhere('ends_at', '>=', $now); }); } public function scopeAutoApply($query) { return $query->where('auto_apply', true); } public function remainingUsages(?int $customerUsage = null): ?int { $globalRemaining = $this->usage_limit !== null ? max($this->usage_limit - $this->redemptions_count, 0) : null; $customerRemaining = $this->per_customer_limit !== null && $customerUsage !== null ? max($this->per_customer_limit - $customerUsage, 0) : null; if ($globalRemaining === null) { return $customerRemaining; } if ($customerRemaining === null) { return $globalRemaining; } return min($globalRemaining, $customerRemaining); } public function isCurrentlyActive(): bool { if ($this->status !== CouponStatus::ACTIVE) { return false; } $now = now(); if ($this->starts_at && $this->starts_at->isFuture()) { return false; } if ($this->ends_at && $this->ends_at->isPast()) { return false; } if ($this->usage_limit !== null && $this->redemptions_count >= $this->usage_limit) { return false; } return true; } public function appliesToPackage(?Package $package): bool { if (! $package) { return true; } if ($this->relationLoaded('packages')) { return $this->packages->isEmpty() || $this->packages->contains(fn (Package $pkg) => $pkg->is($package)); } $count = $this->packages()->count(); if ($count === 0) { return true; } return $this->packages()->whereKey($package->getKey())->exists(); } protected function code(): Attribute { return Attribute::make( get: fn (?string $value) => $value ? Str::upper($value) : $value, set: fn (?string $value) => $value ? Str::upper($value) : $value, ); } }