where('is_active', true) ->where(function ($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }) ->lockForUpdate() ->first(); if (!$discount) { throw new DiscountCodeUnavailableException( 'کد تخفیف معتبر نیست یا منقضی شده است. سفارش بدون تخفیف مجدداً ثبت شود.' ); } // Idempotency: اگر همین کد قبلاً برای همین سفارش مصرف شده، دوباره مصرف نکن $alreadyUsed = DiscountCodeUsage::where('discount_code_id', $discount->id) ->where('shipment_id', $shipment->id) ->exists(); if ($alreadyUsed) { return true; } // افزایش atomic با گارد ظرفیت — حتی اگر دو درخواست همزمان برسند // فقط یکی موفق می‌شود وقتی ظرفیت تک‌مصرف است $updated = DiscountCode::where('id', $discount->id) ->where(function ($q) { $q->whereNull('usage_limit') ->orWhereColumn('used_count', '<', 'usage_limit'); }) ->increment('used_count'); if (!$updated) { throw new DiscountCodeUnavailableException( 'ظرفیت استفاده از این کد تخفیف به پایان رسیده است. سفارش بدون تخفیف مجدداً ثبت شود.' ); } // رکورد Audit DiscountCodeUsage::create([ 'discount_code_id' => $discount->id, 'user_id' => $user->id, 'shipment_id' => $shipment->id, 'discounted_amount' => round($discountedAmount, 2), ]); Log::info('Discount code consumed', [ 'code' => $discount->code, 'discount_code_id' => $discount->id, 'user_id' => $user->id, 'shipment_id' => $shipment->id, 'discounted_amount' => round($discountedAmount, 2), 'used_count' => $discount->used_count + 1, ]); return true; } /** * مصرف ایمن — خطا را لاگ می‌کند و به‌جای پرتاب، false برمی‌گرداند. * برای سناریوهایی که شکست مصرف نباید ثبت سفارش را قطع کند. */ public function tryConsumeForOrder( ?string $code, User $user, Shipment $shipment, float $discountedAmount = 0.0 ): bool { try { return $this->consumeForOrder($code, $user, $shipment, $discountedAmount); } catch (DiscountCodeUnavailableException $e) { Log::warning('Discount code consumption failed', [ 'code' => $code, 'user_id' => $user->id, 'shipment_id' => $shipment->id, 'reason' => $e->getMessage(), ]); return false; } } }