style: pint the files touched by the OrderPaid / shop.models.order fix
Formatting only (laravel preset via pint.json): import ordering, FQCN -> use, `new X()` -> `new X`, `!` spacing, phpdoc trims, braces on one-line continue. No behaviour change; suite re-run green (1485 tests). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0d9823df04
commit
08e459959e
|
|
@ -6,8 +6,12 @@ namespace Blax\Shop\Models;
|
||||||
|
|
||||||
use Blax\Shop\Contracts\Cartable;
|
use Blax\Shop\Contracts\Cartable;
|
||||||
use Blax\Shop\Enums\CartStatus;
|
use Blax\Shop\Enums\CartStatus;
|
||||||
|
use Blax\Shop\Enums\PriceType;
|
||||||
|
use Blax\Shop\Enums\PricingStrategy;
|
||||||
use Blax\Shop\Enums\ProductType;
|
use Blax\Shop\Enums\ProductType;
|
||||||
use Blax\Shop\Enums\PurchaseStatus;
|
use Blax\Shop\Enums\PurchaseStatus;
|
||||||
|
use Blax\Shop\Enums\RecurringInterval;
|
||||||
|
use Blax\Shop\Events\CartCreated;
|
||||||
use Blax\Shop\Exceptions\CartableInterfaceException;
|
use Blax\Shop\Exceptions\CartableInterfaceException;
|
||||||
use Blax\Shop\Exceptions\CartAlreadyConvertedException;
|
use Blax\Shop\Exceptions\CartAlreadyConvertedException;
|
||||||
use Blax\Shop\Exceptions\CartDatesRequiredException;
|
use Blax\Shop\Exceptions\CartDatesRequiredException;
|
||||||
|
|
@ -15,7 +19,9 @@ use Blax\Shop\Exceptions\CartEmptyException;
|
||||||
use Blax\Shop\Exceptions\CartItemMissingInformationException;
|
use Blax\Shop\Exceptions\CartItemMissingInformationException;
|
||||||
use Blax\Shop\Exceptions\ExceedsMaxPerCartException;
|
use Blax\Shop\Exceptions\ExceedsMaxPerCartException;
|
||||||
use Blax\Shop\Exceptions\ExceedsMaxPerUserException;
|
use Blax\Shop\Exceptions\ExceedsMaxPerUserException;
|
||||||
|
use Blax\Shop\Exceptions\HasNoPriceException;
|
||||||
use Blax\Shop\Exceptions\InvalidDateRangeException;
|
use Blax\Shop\Exceptions\InvalidDateRangeException;
|
||||||
|
use Blax\Shop\Exceptions\MixedCheckoutModeException;
|
||||||
use Blax\Shop\Exceptions\NotEnoughAvailableInTimespanException;
|
use Blax\Shop\Exceptions\NotEnoughAvailableInTimespanException;
|
||||||
use Blax\Shop\Exceptions\NotEnoughStockException;
|
use Blax\Shop\Exceptions\NotEnoughStockException;
|
||||||
use Blax\Shop\Exceptions\PriceCalculationException;
|
use Blax\Shop\Exceptions\PriceCalculationException;
|
||||||
|
|
@ -30,11 +36,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Stripe\Checkout\Session;
|
||||||
|
use Stripe\Stripe;
|
||||||
|
use Workbench\App\Models\User;
|
||||||
|
|
||||||
class Cart extends Model
|
class Cart extends Model
|
||||||
{
|
{
|
||||||
use HasUuids, HasExpiration, HasFactory, HasBookingPriceCalculation, ChecksIfBooking;
|
use ChecksIfBooking, HasBookingPriceCalculation, HasExpiration, HasFactory, HasUuids;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'session_id',
|
'session_id',
|
||||||
|
|
@ -66,7 +77,7 @@ class Cart extends Model
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $dispatchesEvents = [
|
protected $dispatchesEvents = [
|
||||||
'created' => \Blax\Shop\Events\CartCreated::class,
|
'created' => CartCreated::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(array $attributes = [])
|
public function __construct(array $attributes = [])
|
||||||
|
|
@ -97,6 +108,7 @@ class Cart extends Model
|
||||||
{
|
{
|
||||||
$this->last_activity_at = now();
|
$this->last_activity_at = now();
|
||||||
$this->saveQuietly(); // Don't trigger events
|
$this->saveQuietly(); // Don't trigger events
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +170,7 @@ class Cart extends Model
|
||||||
{
|
{
|
||||||
$this->status = CartStatus::EXPIRED;
|
$this->status = CartStatus::EXPIRED;
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,6 +181,7 @@ class Cart extends Model
|
||||||
{
|
{
|
||||||
$this->status = CartStatus::ABANDONED;
|
$this->status = CartStatus::ABANDONED;
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +246,7 @@ class Cart extends Model
|
||||||
|
|
||||||
public function purchases(): HasMany
|
public function purchases(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(config('shop.models.product_purchase', \Blax\Shop\Models\ProductPurchase::class), 'cart_id');
|
return $this->hasMany(config('shop.models.product_purchase', ProductPurchase::class), 'cart_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -240,7 +254,7 @@ class Cart extends Model
|
||||||
*/
|
*/
|
||||||
public function order()
|
public function order()
|
||||||
{
|
{
|
||||||
return $this->hasOne(config('shop.models.order', \Blax\Shop\Models\Order::class), 'cart_id');
|
return $this->hasOne(config('shop.models.order', Order::class), 'cart_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotal(): float
|
public function getTotal(): float
|
||||||
|
|
@ -317,8 +331,6 @@ class Cart extends Model
|
||||||
* Check if cart is ready for checkout.
|
* Check if cart is ready for checkout.
|
||||||
*
|
*
|
||||||
* Returns true if all cart items are ready for checkout.
|
* Returns true if all cart items are ready for checkout.
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function getIsReadyToCheckoutAttribute(): bool
|
public function getIsReadyToCheckoutAttribute(): bool
|
||||||
{
|
{
|
||||||
|
|
@ -347,7 +359,7 @@ class Cart extends Model
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Support\Collection Collection of CartItem models requiring adjustments
|
* @return Collection Collection of CartItem models requiring adjustments
|
||||||
*/
|
*/
|
||||||
public function getItemsRequiringAdjustments()
|
public function getItemsRequiringAdjustments()
|
||||||
{
|
{
|
||||||
|
|
@ -377,6 +389,7 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface|string $until End date (DateTimeInterface or parsable string)
|
* @param \DateTimeInterface|string $until End date (DateTimeInterface or parsable string)
|
||||||
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
||||||
* @return $this
|
* @return $this
|
||||||
|
*
|
||||||
* @throws InvalidDateRangeException
|
* @throws InvalidDateRangeException
|
||||||
* @throws NotEnoughAvailableInTimespanException
|
* @throws NotEnoughAvailableInTimespanException
|
||||||
*/
|
*/
|
||||||
|
|
@ -445,6 +458,7 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface|string $from Start date (DateTimeInterface or parsable string)
|
* @param \DateTimeInterface|string $from Start date (DateTimeInterface or parsable string)
|
||||||
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
||||||
* @return $this
|
* @return $this
|
||||||
|
*
|
||||||
* @throws NotEnoughAvailableInTimespanException
|
* @throws NotEnoughAvailableInTimespanException
|
||||||
*/
|
*/
|
||||||
public function setFromDate(
|
public function setFromDate(
|
||||||
|
|
@ -492,6 +506,7 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface|string $until End date (DateTimeInterface or parsable string)
|
* @param \DateTimeInterface|string $until End date (DateTimeInterface or parsable string)
|
||||||
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
* @param bool $validateAvailability Whether to validate product availability for the timespan
|
||||||
* @return $this
|
* @return $this
|
||||||
|
*
|
||||||
* @throws NotEnoughAvailableInTimespanException
|
* @throws NotEnoughAvailableInTimespanException
|
||||||
*/
|
*/
|
||||||
public function setUntilDate(\DateTimeInterface|string|int|float $until, bool $validateAvailability = true): self
|
public function setUntilDate(\DateTimeInterface|string|int|float $until, bool $validateAvailability = true): self
|
||||||
|
|
@ -539,6 +554,7 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface|null $from Optional from date (uses cart's from if not provided)
|
* @param \DateTimeInterface|null $from Optional from date (uses cart's from if not provided)
|
||||||
* @param \DateTimeInterface|null $until Optional until date (uses cart's until if not provided)
|
* @param \DateTimeInterface|null $until Optional until date (uses cart's until if not provided)
|
||||||
* @return $this
|
* @return $this
|
||||||
|
*
|
||||||
* @throws NotEnoughAvailableInTimespanException
|
* @throws NotEnoughAvailableInTimespanException
|
||||||
*/
|
*/
|
||||||
public function applyDatesToItems(
|
public function applyDatesToItems(
|
||||||
|
|
@ -593,6 +609,7 @@ class Cart extends Model
|
||||||
'from' => $itemFrom,
|
'from' => $itemFrom,
|
||||||
'until' => $itemUntil,
|
'until' => $itemUntil,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -620,6 +637,7 @@ class Cart extends Model
|
||||||
'subtotal' => null,
|
'subtotal' => null,
|
||||||
'unit_amount' => null,
|
'unit_amount' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Skip updateDates() since we already set the dates with null price
|
// Skip updateDates() since we already set the dates with null price
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -646,7 +664,6 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface $from New start date
|
* @param \DateTimeInterface $from New start date
|
||||||
* @param \DateTimeInterface $until New end date
|
* @param \DateTimeInterface $until New end date
|
||||||
* @param bool $overwrite Whether to apply to all items or only those without dates
|
* @param bool $overwrite Whether to apply to all items or only those without dates
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected function reallocatePoolItems(\DateTimeInterface $from, \DateTimeInterface $until, bool $overwrite = true): void
|
protected function reallocatePoolItems(\DateTimeInterface $from, \DateTimeInterface $until, bool $overwrite = true): void
|
||||||
{
|
{
|
||||||
|
|
@ -654,6 +671,7 @@ class Cart extends Model
|
||||||
$poolItems = $this->items()->get()
|
$poolItems = $this->items()->get()
|
||||||
->filter(function ($item) {
|
->filter(function ($item) {
|
||||||
$product = $item->purchasable;
|
$product = $item->purchasable;
|
||||||
|
|
||||||
return $product instanceof Product && $product->isPool();
|
return $product instanceof Product && $product->isPool();
|
||||||
})
|
})
|
||||||
->groupBy('purchasable_id');
|
->groupBy('purchasable_id');
|
||||||
|
|
@ -719,15 +737,16 @@ class Cart extends Model
|
||||||
'unit_amount' => null,
|
'unit_amount' => null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by pricing strategy
|
// Sort by pricing strategy
|
||||||
usort($singlesWithPrices, function ($a, $b) use ($strategy) {
|
usort($singlesWithPrices, function ($a, $b) use ($strategy) {
|
||||||
return match ($strategy) {
|
return match ($strategy) {
|
||||||
\Blax\Shop\Enums\PricingStrategy::LOWEST => $a['price'] <=> $b['price'],
|
PricingStrategy::LOWEST => $a['price'] <=> $b['price'],
|
||||||
\Blax\Shop\Enums\PricingStrategy::HIGHEST => $b['price'] <=> $a['price'],
|
PricingStrategy::HIGHEST => $b['price'] <=> $a['price'],
|
||||||
\Blax\Shop\Enums\PricingStrategy::AVERAGE => 0,
|
PricingStrategy::AVERAGE => 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -781,13 +800,17 @@ class Cart extends Model
|
||||||
$firstAllocation = true;
|
$firstAllocation = true;
|
||||||
|
|
||||||
foreach ($orderedSingles as $singleInfo) {
|
foreach ($orderedSingles as $singleInfo) {
|
||||||
if ($remainingQty <= 0) break;
|
if ($remainingQty <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$single = $singleInfo['single'];
|
$single = $singleInfo['single'];
|
||||||
$usedFromSingle = $singleUsage[$single->id] ?? 0;
|
$usedFromSingle = $singleUsage[$single->id] ?? 0;
|
||||||
$availableFromSingle = $singleInfo['available'] - $usedFromSingle;
|
$availableFromSingle = $singleInfo['available'] - $usedFromSingle;
|
||||||
|
|
||||||
if ($availableFromSingle <= 0) continue;
|
if ($availableFromSingle <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$qtyToAllocate = min($remainingQty, $availableFromSingle);
|
$qtyToAllocate = min($remainingQty, $availableFromSingle);
|
||||||
|
|
||||||
|
|
@ -867,7 +890,7 @@ class Cart extends Model
|
||||||
*
|
*
|
||||||
* @param \DateTimeInterface $from Start date
|
* @param \DateTimeInterface $from Start date
|
||||||
* @param \DateTimeInterface $until End date
|
* @param \DateTimeInterface $until End date
|
||||||
* @return void
|
*
|
||||||
* @throws NotEnoughAvailableInTimespanException
|
* @throws NotEnoughAvailableInTimespanException
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
|
|
@ -877,7 +900,6 @@ class Cart extends Model
|
||||||
* @param \DateTimeInterface $from Start date
|
* @param \DateTimeInterface $from Start date
|
||||||
* @param \DateTimeInterface $until End date
|
* @param \DateTimeInterface $until End date
|
||||||
* @param bool $useProvidedDates Whether to use provided dates or item's own dates
|
* @param bool $useProvidedDates Whether to use provided dates or item's own dates
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected function validateDateAvailability(\DateTimeInterface $from, \DateTimeInterface $until, bool $useProvidedDates = false): void
|
protected function validateDateAvailability(\DateTimeInterface $from, \DateTimeInterface $until, bool $useProvidedDates = false): void
|
||||||
{
|
{
|
||||||
|
|
@ -951,7 +973,8 @@ class Cart extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
// If just an ID is passed, try to determine the user model class
|
// If just an ID is passed, try to determine the user model class
|
||||||
$userModel = config('auth.providers.users.model', \Workbench\App\Models\User::class);
|
$userModel = config('auth.providers.users.model', User::class);
|
||||||
|
|
||||||
return $query->where('customer_id', $userOrId)
|
return $query->where('customer_id', $userOrId)
|
||||||
->where('customer_type', $userModel);
|
->where('customer_type', $userModel);
|
||||||
}
|
}
|
||||||
|
|
@ -965,9 +988,6 @@ class Cart extends Model
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the cart ID in the session for retrieval across requests
|
* Store the cart ID in the session for retrieval across requests
|
||||||
*
|
|
||||||
* @param Cart $cart
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public static function setSession(Cart $cart): void
|
public static function setSession(Cart $cart): void
|
||||||
{
|
{
|
||||||
|
|
@ -982,19 +1002,19 @@ class Cart extends Model
|
||||||
* @param array<string, mixed> $parameters Additional parameters for the cart item
|
* @param array<string, mixed> $parameters Additional parameters for the cart item
|
||||||
* @param \DateTimeInterface|null $from Optional start date for bookings
|
* @param \DateTimeInterface|null $from Optional start date for bookings
|
||||||
* @param \DateTimeInterface|null $until Optional end date for bookings
|
* @param \DateTimeInterface|null $until Optional end date for bookings
|
||||||
* @return CartItem
|
*
|
||||||
* @throws \Exception If the item doesn't implement Cartable interface
|
* @throws \Exception If the item doesn't implement Cartable interface
|
||||||
*/
|
*/
|
||||||
public function addToCart(
|
public function addToCart(
|
||||||
Model $cartable,
|
Model $cartable,
|
||||||
int $quantity = 1,
|
int $quantity = 1,
|
||||||
array $parameters = [],
|
array $parameters = [],
|
||||||
null|\DateTimeInterface $from = null,
|
?\DateTimeInterface $from = null,
|
||||||
null|\DateTimeInterface $until = null
|
?\DateTimeInterface $until = null
|
||||||
): CartItem {
|
): CartItem {
|
||||||
// $cartable must implement Cartable
|
// $cartable must implement Cartable
|
||||||
if (! $cartable instanceof Cartable) {
|
if (! $cartable instanceof Cartable) {
|
||||||
throw new CartableInterfaceException();
|
throw new CartableInterfaceException;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults for cartables that aren't Product / ProductPrice (e.g. an app
|
// Defaults for cartables that aren't Product / ProductPrice (e.g. an app
|
||||||
|
|
@ -1055,6 +1075,7 @@ class Cart extends Model
|
||||||
if (! $item->from || ! $item->until) {
|
if (! $item->from || ! $item->until) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for overlap: item overlaps if it doesn't end before period starts or start after period ends
|
// Check for overlap: item overlaps if it doesn't end before period starts or start after period ends
|
||||||
return ! ($item->until < $from || $item->from > $until);
|
return ! ($item->until < $from || $item->from > $until);
|
||||||
})
|
})
|
||||||
|
|
@ -1094,6 +1115,7 @@ class Cart extends Model
|
||||||
for ($i = 0; $i < $quantity; $i++) {
|
for ($i = 0; $i < $quantity; $i++) {
|
||||||
$lastCartItem = $this->addToCart($cartable, 1, $parameters, $from, $until);
|
$lastCartItem = $this->addToCart($cartable, 1, $parameters, $from, $until);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $lastCartItem;
|
return $lastCartItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1154,7 +1176,7 @@ class Cart extends Model
|
||||||
}
|
}
|
||||||
} elseif ($from || $until) {
|
} elseif ($from || $until) {
|
||||||
// If only one date is provided, it's an error
|
// If only one date is provided, it's an error
|
||||||
throw new CartDatesRequiredException();
|
throw new CartDatesRequiredException;
|
||||||
} else {
|
} else {
|
||||||
// When adding pool items without dates, validate against total pool capacity
|
// When adding pool items without dates, validate against total pool capacity
|
||||||
// This allows adding items even if currently claimed - date-based validation happens later
|
// This allows adding items even if currently claimed - date-based validation happens later
|
||||||
|
|
@ -1310,7 +1332,7 @@ class Cart extends Model
|
||||||
if ($pricePerDay === null) {
|
if ($pricePerDay === null) {
|
||||||
if ($is_pool) {
|
if ($is_pool) {
|
||||||
// For pool products, throw specific error when neither pool nor single items have prices
|
// For pool products, throw specific error when neither pool nor single items have prices
|
||||||
throw \Blax\Shop\Exceptions\HasNoPriceException::poolProductNoPriceAndNoSingleItemPrices($cartable->name);
|
throw HasNoPriceException::poolProductNoPriceAndNoSingleItemPrices($cartable->name);
|
||||||
}
|
}
|
||||||
throw new ProductHasNoPriceException($cartable->name);
|
throw new ProductHasNoPriceException($cartable->name);
|
||||||
}
|
}
|
||||||
|
|
@ -1370,7 +1392,7 @@ class Cart extends Model
|
||||||
$priceId = $defaultPrice?->id;
|
$priceId = $defaultPrice?->id;
|
||||||
$currency = $defaultPrice?->currency;
|
$currency = $defaultPrice?->currency;
|
||||||
}
|
}
|
||||||
} elseif ($cartable instanceof \Blax\Shop\Models\ProductPrice) {
|
} elseif ($cartable instanceof ProductPrice) {
|
||||||
// If adding a ProductPrice directly, use its ID and currency
|
// If adding a ProductPrice directly, use its ID and currency
|
||||||
$priceId = $cartable->id;
|
$priceId = $cartable->id;
|
||||||
$currency = $cartable->currency;
|
$currency = $cartable->currency;
|
||||||
|
|
@ -1524,6 +1546,7 @@ class Cart extends Model
|
||||||
: (array) $item->parameters;
|
: (array) $item->parameters;
|
||||||
ksort($existingParams);
|
ksort($existingParams);
|
||||||
ksort($parameters);
|
ksort($parameters);
|
||||||
|
|
||||||
return $existingParams === $parameters;
|
return $existingParams === $parameters;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1831,7 +1854,9 @@ class Cart extends Model
|
||||||
|
|
||||||
$dates = [];
|
$dates = [];
|
||||||
foreach (($availability['dates'] ?? []) as $iso => $row) {
|
foreach (($availability['dates'] ?? []) as $iso => $row) {
|
||||||
if ($iso < $visibleStartIso || $iso > $visibleEndIso) continue;
|
if ($iso < $visibleStartIso || $iso > $visibleEndIso) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$dates[$iso] = ($row['max'] ?? 0) >= 1;
|
$dates[$iso] = ($row['max'] ?? 0) >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1861,8 +1886,7 @@ class Cart extends Model
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
$isAvailableOn = fn (string $iso): bool =>
|
$isAvailableOn = fn (string $iso): bool => isset($dayRows[$iso]) && ($dayRows[$iso]['max'] ?? 0) >= $required;
|
||||||
isset($dayRows[$iso]) && ($dayRows[$iso]['max'] ?? 0) >= $required;
|
|
||||||
|
|
||||||
$availableForSelected = $selectedFromIso && $selectedUntilIso;
|
$availableForSelected = $selectedFromIso && $selectedUntilIso;
|
||||||
if ($availableForSelected) {
|
if ($availableForSelected) {
|
||||||
|
|
@ -1880,10 +1904,15 @@ class Cart extends Model
|
||||||
$windowFitsAt = function (string $startIso) use (&$isAvailableOn, $durationDays, $searchEnd): bool {
|
$windowFitsAt = function (string $startIso) use (&$isAvailableOn, $durationDays, $searchEnd): bool {
|
||||||
$cursor = Carbon::parse($startIso);
|
$cursor = Carbon::parse($startIso);
|
||||||
for ($i = 0; $i < $durationDays; $i++) {
|
for ($i = 0; $i < $durationDays; $i++) {
|
||||||
if ($cursor->gt($searchEnd)) return false;
|
if ($cursor->gt($searchEnd)) {
|
||||||
if (! $isAvailableOn($cursor->toDateString())) return false;
|
return false;
|
||||||
|
}
|
||||||
|
if (! $isAvailableOn($cursor->toDateString())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$cursor->addDay();
|
$cursor->addDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1930,7 +1959,9 @@ class Cart extends Model
|
||||||
$datesPartial = [];
|
$datesPartial = [];
|
||||||
$partialWindows = [];
|
$partialWindows = [];
|
||||||
foreach ($dayRows as $iso => $row) {
|
foreach ($dayRows as $iso => $row) {
|
||||||
if ($iso < $visibleStartIso || $iso > $visibleEndIso) continue;
|
if ($iso < $visibleStartIso || $iso > $visibleEndIso) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$max = $row['max'] ?? 0;
|
$max = $row['max'] ?? 0;
|
||||||
$min = $row['min'] ?? 0;
|
$min = $row['min'] ?? 0;
|
||||||
if ($max < $required) {
|
if ($max < $required) {
|
||||||
|
|
@ -2012,7 +2043,7 @@ class Cart extends Model
|
||||||
// Check if cart is already converted
|
// Check if cart is already converted
|
||||||
if ($this->isConverted()) {
|
if ($this->isConverted()) {
|
||||||
if ($throws) {
|
if ($throws) {
|
||||||
throw new CartAlreadyConvertedException();
|
throw new CartAlreadyConvertedException;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -2024,7 +2055,7 @@ class Cart extends Model
|
||||||
|
|
||||||
if ($items->isEmpty()) {
|
if ($items->isEmpty()) {
|
||||||
if ($throws) {
|
if ($throws) {
|
||||||
throw new CartEmptyException();
|
throw new CartEmptyException;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -2067,8 +2098,7 @@ class Cart extends Model
|
||||||
// Calculate how much of this cart's items are already counted
|
// Calculate how much of this cart's items are already counted
|
||||||
// We need to check if there's still enough stock for what's in this cart
|
// We need to check if there's still enough stock for what's in this cart
|
||||||
$cartItemsForPool = $items->filter(
|
$cartItemsForPool = $items->filter(
|
||||||
fn($i) =>
|
fn ($i) => $i->purchasable_id === $product->id &&
|
||||||
$i->purchasable_id === $product->id &&
|
|
||||||
$i->purchasable_type === get_class($product)
|
$i->purchasable_type === get_class($product)
|
||||||
);
|
);
|
||||||
$totalInCart = $cartItemsForPool->sum('quantity');
|
$totalInCart = $cartItemsForPool->sum('quantity');
|
||||||
|
|
@ -2087,8 +2117,7 @@ class Cart extends Model
|
||||||
// Without dates, check general pool availability
|
// Without dates, check general pool availability
|
||||||
$available = $product->getPoolMaxQuantity();
|
$available = $product->getPoolMaxQuantity();
|
||||||
$totalInCart = $items->filter(
|
$totalInCart = $items->filter(
|
||||||
fn($i) =>
|
fn ($i) => $i->purchasable_id === $product->id &&
|
||||||
$i->purchasable_id === $product->id &&
|
|
||||||
$i->purchasable_type === get_class($product)
|
$i->purchasable_type === get_class($product)
|
||||||
)->sum('quantity');
|
)->sum('quantity');
|
||||||
|
|
||||||
|
|
@ -2177,10 +2206,10 @@ class Cart extends Model
|
||||||
*
|
*
|
||||||
* @return static The converted cart (fresh state within the transaction scope).
|
* @return static The converted cart (fresh state within the transaction scope).
|
||||||
*
|
*
|
||||||
* @throws \Blax\Shop\Exceptions\CartAlreadyConvertedException
|
* @throws CartAlreadyConvertedException
|
||||||
* @throws \Blax\Shop\Exceptions\CartEmptyException
|
* @throws CartEmptyException
|
||||||
* @throws \Blax\Shop\Exceptions\CartItemMissingInformationException
|
* @throws CartItemMissingInformationException
|
||||||
* @throws \Blax\Shop\Exceptions\NotEnoughStockException
|
* @throws NotEnoughStockException
|
||||||
* @throws \Throwable For any other unexpected failures during checkout/stock claiming.
|
* @throws \Throwable For any other unexpected failures during checkout/stock claiming.
|
||||||
*/
|
*/
|
||||||
public function checkout(): static
|
public function checkout(): static
|
||||||
|
|
@ -2325,15 +2354,15 @@ class Cart extends Model
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = $price->type instanceof \Blax\Shop\Enums\PriceType
|
$type = $price->type instanceof PriceType
|
||||||
? $price->type->value
|
? $price->type->value
|
||||||
: (string) $price->type;
|
: (string) $price->type;
|
||||||
|
|
||||||
if ($type !== \Blax\Shop\Enums\PriceType::RECURRING->value) {
|
if ($type !== PriceType::RECURRING->value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$interval = $price->interval instanceof \Blax\Shop\Enums\RecurringInterval
|
$interval = $price->interval instanceof RecurringInterval
|
||||||
? $price->interval->value
|
? $price->interval->value
|
||||||
: (is_string($price->interval) ? $price->interval : null);
|
: (is_string($price->interval) ? $price->interval : null);
|
||||||
|
|
||||||
|
|
@ -2346,7 +2375,7 @@ class Cart extends Model
|
||||||
$count = 1;
|
$count = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($interval === \Blax\Shop\Enums\RecurringInterval::QUARTER->value) {
|
if ($interval === RecurringInterval::QUARTER->value) {
|
||||||
return ['interval' => 'month', 'interval_count' => 3 * $count];
|
return ['interval' => 'month', 'interval_count' => 3 * $count];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2368,8 +2397,8 @@ class Cart extends Model
|
||||||
*
|
*
|
||||||
* @param array $options Optional session parameters (success_url, cancel_url, etc.)
|
* @param array $options Optional session parameters (success_url, cancel_url, etc.)
|
||||||
* @param string|null $url Optional fullPath URL for success and cancel URLs
|
* @param string|null $url Optional fullPath URL for success and cancel URLs
|
||||||
*
|
|
||||||
* @return mixed Stripe\Checkout\Session instance
|
* @return mixed Stripe\Checkout\Session instance
|
||||||
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function checkoutSession(array $options = [], ?string $url = null)
|
public function checkoutSession(array $options = [], ?string $url = null)
|
||||||
|
|
@ -2379,7 +2408,7 @@ class Cart extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure Stripe is initialized
|
// Ensure Stripe is initialized
|
||||||
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
|
Stripe::setApiKey(config('services.stripe.secret'));
|
||||||
|
|
||||||
// Validate cart before proceeding (doesn't convert it)
|
// Validate cart before proceeding (doesn't convert it)
|
||||||
$this->validateForCheckout();
|
$this->validateForCheckout();
|
||||||
|
|
@ -2469,6 +2498,7 @@ class Cart extends Model
|
||||||
'price' => $priceModel->stripe_price_id,
|
'price' => $priceModel->stripe_price_id,
|
||||||
'quantity' => $item->quantity,
|
'quantity' => $item->quantity,
|
||||||
];
|
];
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2484,6 +2514,7 @@ class Cart extends Model
|
||||||
],
|
],
|
||||||
'quantity' => $item->quantity,
|
'quantity' => $item->quantity,
|
||||||
];
|
];
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2509,7 +2540,7 @@ class Cart extends Model
|
||||||
// session — surface that explicitly rather than letting Stripe reject
|
// session — surface that explicitly rather than letting Stripe reject
|
||||||
// it with an opaque error.
|
// it with an opaque error.
|
||||||
if ($hasRecurring && $hasOneTime) {
|
if ($hasRecurring && $hasOneTime) {
|
||||||
throw new \Blax\Shop\Exceptions\MixedCheckoutModeException();
|
throw new MixedCheckoutModeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mode = $hasRecurring ? 'subscription' : 'payment';
|
$mode = $hasRecurring ? 'subscription' : 'payment';
|
||||||
|
|
@ -2565,7 +2596,7 @@ class Cart extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$session = \Stripe\Checkout\Session::create($sessionParams);
|
$session = Session::create($sessionParams);
|
||||||
|
|
||||||
// Store session ID in cart meta
|
// Store session ID in cart meta
|
||||||
$meta = $this->meta ?? (object) [];
|
$meta = $this->meta ?? (object) [];
|
||||||
|
|
@ -2575,14 +2606,14 @@ class Cart extends Model
|
||||||
$meta->stripe_session_id = $session->id;
|
$meta->stripe_session_id = $session->id;
|
||||||
$this->update(['meta' => $meta]);
|
$this->update(['meta' => $meta]);
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Log::info('Stripe checkout session created', [
|
Log::info('Stripe checkout session created', [
|
||||||
'cart_id' => $this->id,
|
'cart_id' => $this->id,
|
||||||
'session_id' => $session->id,
|
'session_id' => $session->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $session;
|
return $session;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Illuminate\Support\Facades\Log::error('Stripe checkout session creation failed', [
|
Log::error('Stripe checkout session creation failed', [
|
||||||
'cart_id' => $this->id,
|
'cart_id' => $this->id,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
@ -2598,8 +2629,6 @@ class Cart extends Model
|
||||||
* - string: The checkout session URL if a session exists and is valid
|
* - string: The checkout session URL if a session exists and is valid
|
||||||
* - null: If no session exists or Stripe is not enabled
|
* - null: If no session exists or Stripe is not enabled
|
||||||
* - false: If an error occurred while retrieving the session
|
* - false: If an error occurred while retrieving the session
|
||||||
*
|
|
||||||
* @return string|null|false
|
|
||||||
*/
|
*/
|
||||||
public function checkoutSessionLink(array $option = [], ?string $url = null): string|null|false
|
public function checkoutSessionLink(array $option = [], ?string $url = null): string|null|false
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ namespace Blax\Shop\Models;
|
||||||
|
|
||||||
use Blax\Shop\Enums\OrderStatus;
|
use Blax\Shop\Enums\OrderStatus;
|
||||||
use Blax\Shop\Enums\PurchaseStatus;
|
use Blax\Shop\Enums\PurchaseStatus;
|
||||||
|
use Blax\Shop\Events\OrderCreated;
|
||||||
|
use Blax\Shop\Events\OrderPaid;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -14,6 +16,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,7 +27,7 @@ use Illuminate\Support\Facades\DB;
|
||||||
*/
|
*/
|
||||||
class Order extends Model
|
class Order extends Model
|
||||||
{
|
{
|
||||||
use HasUuids, HasFactory, SoftDeletes;
|
use HasFactory, HasUuids, SoftDeletes;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'order_number',
|
'order_number',
|
||||||
|
|
@ -89,7 +92,7 @@ class Order extends Model
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $dispatchesEvents = [
|
protected $dispatchesEvents = [
|
||||||
'created' => \Blax\Shop\Events\OrderCreated::class,
|
'created' => OrderCreated::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(array $attributes = [])
|
public function __construct(array $attributes = [])
|
||||||
|
|
@ -155,7 +158,7 @@ class Order extends Model
|
||||||
if ($difference > 0) {
|
if ($difference > 0) {
|
||||||
$currency = $order->currency ?? config('shop.currency', 'USD');
|
$currency = $order->currency ?? config('shop.currency', 'USD');
|
||||||
$order->addNote(
|
$order->addNote(
|
||||||
"Payment received: " . static::formatMoney($difference, $currency),
|
'Payment received: '.static::formatMoney($difference, $currency),
|
||||||
'payment',
|
'payment',
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
@ -175,7 +178,7 @@ class Order extends Model
|
||||||
// and a host subclass configured via shop.models.order gets it too.
|
// and a host subclass configured via shop.models.order gets it too.
|
||||||
static::updated(function (Order $order) {
|
static::updated(function (Order $order) {
|
||||||
if ($order->wasChanged('paid_at') && $order->paid_at && ! $order->getOriginal('paid_at')) {
|
if ($order->wasChanged('paid_at') && $order->paid_at && ! $order->getOriginal('paid_at')) {
|
||||||
\Blax\Shop\Events\OrderPaid::dispatch($order);
|
OrderPaid::dispatch($order);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +214,7 @@ class Order extends Model
|
||||||
public static function formatMoney(int $amount, string $currency = 'USD'): string
|
public static function formatMoney(int $amount, string $currency = 'USD'): string
|
||||||
{
|
{
|
||||||
$formatted = number_format($amount / 100, 2);
|
$formatted = number_format($amount / 100, 2);
|
||||||
|
|
||||||
return strtoupper($currency).' '.$formatted;
|
return strtoupper($currency).' '.$formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,7 +466,7 @@ class Order extends Model
|
||||||
|
|
||||||
$currency = $this->currency ?? config('shop.currency', 'USD');
|
$currency = $this->currency ?? config('shop.currency', 'USD');
|
||||||
$this->addNote(
|
$this->addNote(
|
||||||
"Refund processed: " . static::formatMoney($amount, $currency) .
|
'Refund processed: '.static::formatMoney($amount, $currency).
|
||||||
($reason ? " - Reason: {$reason}" : ''),
|
($reason ? " - Reason: {$reason}" : ''),
|
||||||
'refund',
|
'refund',
|
||||||
true
|
true
|
||||||
|
|
@ -487,7 +491,7 @@ class Order extends Model
|
||||||
* @param string $content The note content
|
* @param string $content The note content
|
||||||
* @param string $type The note type (note, status_change, payment, etc.)
|
* @param string $type The note type (note, status_change, payment, etc.)
|
||||||
* @param bool $isCustomerNote Whether the note is visible to the customer
|
* @param bool $isCustomerNote Whether the note is visible to the customer
|
||||||
* @param \Illuminate\Database\Eloquent\Model|null $author The author model (User, Admin, etc.)
|
* @param Model|null $author The author model (User, Admin, etc.)
|
||||||
* @param array|object|null $meta Additional metadata
|
* @param array|object|null $meta Additional metadata
|
||||||
*/
|
*/
|
||||||
public function addNote(
|
public function addNote(
|
||||||
|
|
@ -533,7 +537,7 @@ class Order extends Model
|
||||||
public function getMeta(?string $key = null, $default = null)
|
public function getMeta(?string $key = null, $default = null)
|
||||||
{
|
{
|
||||||
if ($key === null) {
|
if ($key === null) {
|
||||||
return $this->meta ?? new \stdClass();
|
return $this->meta ?? new \stdClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->meta?->{$key} ?? $default;
|
return $this->meta?->{$key} ?? $default;
|
||||||
|
|
@ -544,7 +548,7 @@ class Order extends Model
|
||||||
*/
|
*/
|
||||||
public function updateMetaKey(string $key, $value): self
|
public function updateMetaKey(string $key, $value): self
|
||||||
{
|
{
|
||||||
$meta = (array) ($this->meta ?? new \stdClass());
|
$meta = (array) ($this->meta ?? new \stdClass);
|
||||||
$meta[$key] = $value;
|
$meta[$key] = $value;
|
||||||
$this->meta = (object) $meta;
|
$this->meta = (object) $meta;
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
@ -789,6 +793,7 @@ class Order extends Model
|
||||||
public static function getNetRevenue(): int
|
public static function getNetRevenue(): int
|
||||||
{
|
{
|
||||||
$result = static::selectRaw('COALESCE(SUM(amount_paid), 0) - COALESCE(SUM(amount_refunded), 0) as net')->first();
|
$result = static::selectRaw('COALESCE(SUM(amount_paid), 0) - COALESCE(SUM(amount_refunded), 0) as net')->first();
|
||||||
|
|
||||||
return (int) $result->net;
|
return (int) $result->net;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -827,7 +832,7 @@ class Order extends Model
|
||||||
public static function getRevenueSummary(\DateTimeInterface $from, \DateTimeInterface $until): array
|
public static function getRevenueSummary(\DateTimeInterface $from, \DateTimeInterface $until): array
|
||||||
{
|
{
|
||||||
$stats = static::createdBetween($from, $until)
|
$stats = static::createdBetween($from, $until)
|
||||||
->selectRaw("
|
->selectRaw('
|
||||||
COUNT(*) as total_orders,
|
COUNT(*) as total_orders,
|
||||||
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as completed,
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as completed,
|
||||||
SUM(CASE WHEN amount_paid > 0 THEN 1 ELSE 0 END) as paid,
|
SUM(CASE WHEN amount_paid > 0 THEN 1 ELSE 0 END) as paid,
|
||||||
|
|
@ -837,7 +842,7 @@ class Order extends Model
|
||||||
COALESCE(SUM(amount_refunded), 0) as refunded,
|
COALESCE(SUM(amount_refunded), 0) as refunded,
|
||||||
COALESCE(AVG(amount_total), 0) as avg_order_value,
|
COALESCE(AVG(amount_total), 0) as avg_order_value,
|
||||||
COALESCE(AVG(amount_paid), 0) as avg_paid_amount
|
COALESCE(AVG(amount_paid), 0) as avg_paid_amount
|
||||||
", [OrderStatus::COMPLETED->value])
|
', [OrderStatus::COMPLETED->value])
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -867,7 +872,7 @@ class Order extends Model
|
||||||
/**
|
/**
|
||||||
* Get daily revenue breakdown for a date range.
|
* Get daily revenue breakdown for a date range.
|
||||||
*/
|
*/
|
||||||
public static function getDailyRevenue(\DateTimeInterface $from, \DateTimeInterface $until): \Illuminate\Support\Collection
|
public static function getDailyRevenue(\DateTimeInterface $from, \DateTimeInterface $until): Collection
|
||||||
{
|
{
|
||||||
return static::createdBetween($from, $until)
|
return static::createdBetween($from, $until)
|
||||||
->selectRaw('DATE(created_at) as date')
|
->selectRaw('DATE(created_at) as date')
|
||||||
|
|
@ -883,7 +888,7 @@ class Order extends Model
|
||||||
/**
|
/**
|
||||||
* Get monthly revenue breakdown for a date range.
|
* Get monthly revenue breakdown for a date range.
|
||||||
*/
|
*/
|
||||||
public static function getMonthlyRevenue(\DateTimeInterface $from, \DateTimeInterface $until): \Illuminate\Support\Collection
|
public static function getMonthlyRevenue(\DateTimeInterface $from, \DateTimeInterface $until): Collection
|
||||||
{
|
{
|
||||||
return static::createdBetween($from, $until)
|
return static::createdBetween($from, $until)
|
||||||
->selectRaw('YEAR(created_at) as year')
|
->selectRaw('YEAR(created_at) as year')
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,15 @@ use Blax\Shop\Models\Order;
|
||||||
use Blax\Shop\Models\Product;
|
use Blax\Shop\Models\Product;
|
||||||
use Blax\Shop\Models\ProductCategory;
|
use Blax\Shop\Models\ProductCategory;
|
||||||
use Blax\Shop\Models\ProductPrice;
|
use Blax\Shop\Models\ProductPrice;
|
||||||
use Blax\Shop\Models\ProductPurchase;
|
|
||||||
use Blax\Shop\Models\StripeTransaction;
|
use Blax\Shop\Models\StripeTransaction;
|
||||||
use Blax\Shop\Models\Subscription;
|
use Blax\Shop\Models\Subscription;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Carbon\Carbon;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Stripe\Charge;
|
||||||
|
use Stripe\Stripe;
|
||||||
|
|
||||||
class ShopService
|
class ShopService
|
||||||
{
|
{
|
||||||
|
|
@ -27,8 +30,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all products query builder
|
* Get all products query builder
|
||||||
*
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function products(): Builder
|
public function products(): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -39,7 +40,6 @@ class ShopService
|
||||||
* Get a product by ID
|
* Get a product by ID
|
||||||
*
|
*
|
||||||
* @param mixed $id
|
* @param mixed $id
|
||||||
* @return Product|null
|
|
||||||
*/
|
*/
|
||||||
public function product($id): ?Product
|
public function product($id): ?Product
|
||||||
{
|
{
|
||||||
|
|
@ -48,8 +48,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all categories query builder
|
* Get all categories query builder
|
||||||
*
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function categories(): Builder
|
public function categories(): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -58,8 +56,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get in-stock products
|
* Get in-stock products
|
||||||
*
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function inStock(): Builder
|
public function inStock(): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -68,8 +64,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get featured products
|
* Get featured products
|
||||||
*
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function featured(): Builder
|
public function featured(): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -78,8 +72,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get published and visible products
|
* Get published and visible products
|
||||||
*
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function published(): Builder
|
public function published(): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -88,9 +80,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search products by query
|
* Search products by query
|
||||||
*
|
|
||||||
* @param string $query
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function search(string $query): Builder
|
public function search(string $query): Builder
|
||||||
{
|
{
|
||||||
|
|
@ -103,10 +92,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if product has available stock for quantity
|
* Check if product has available stock for quantity
|
||||||
*
|
|
||||||
* @param Product $product
|
|
||||||
* @param int $quantity
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function checkStock(Product $product, int $quantity): bool
|
public function checkStock(Product $product, int $quantity): bool
|
||||||
{
|
{
|
||||||
|
|
@ -119,9 +104,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available stock for a product
|
* Get available stock for a product
|
||||||
*
|
|
||||||
* @param Product $product
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
public function getAvailableStock(Product $product): int
|
public function getAvailableStock(Product $product): int
|
||||||
{
|
{
|
||||||
|
|
@ -134,9 +116,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if product is on sale
|
* Check if product is on sale
|
||||||
*
|
|
||||||
* @param Product $product
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function isOnSale(Product $product): bool
|
public function isOnSale(Product $product): bool
|
||||||
{
|
{
|
||||||
|
|
@ -405,7 +384,7 @@ class ShopService
|
||||||
$startOfYear = Carbon::now()->startOfYear();
|
$startOfYear = Carbon::now()->startOfYear();
|
||||||
$endOfYear = Carbon::now()->endOfYear();
|
$endOfYear = Carbon::now()->endOfYear();
|
||||||
|
|
||||||
$orderStats = $this->orderModel()::selectRaw("
|
$orderStats = $this->orderModel()::selectRaw('
|
||||||
COUNT(*) as total,
|
COUNT(*) as total,
|
||||||
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as pending,
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as pending,
|
||||||
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as processing,
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as processing,
|
||||||
|
|
@ -421,7 +400,7 @@ class ShopService
|
||||||
COALESCE(SUM(CASE WHEN created_at BETWEEN ? AND ? THEN amount_paid ELSE 0 END), 0) as revenue_this_year,
|
COALESCE(SUM(CASE WHEN created_at BETWEEN ? AND ? THEN amount_paid ELSE 0 END), 0) as revenue_this_year,
|
||||||
COALESCE(SUM(amount_refunded), 0) as total_refunded,
|
COALESCE(SUM(amount_refunded), 0) as total_refunded,
|
||||||
COALESCE(AVG(amount_total), 0) as average_order
|
COALESCE(AVG(amount_total), 0) as average_order
|
||||||
", [
|
', [
|
||||||
OrderStatus::PENDING->value,
|
OrderStatus::PENDING->value,
|
||||||
OrderStatus::PROCESSING->value,
|
OrderStatus::PROCESSING->value,
|
||||||
OrderStatus::COMPLETED->value,
|
OrderStatus::COMPLETED->value,
|
||||||
|
|
@ -925,7 +904,6 @@ class ShopService
|
||||||
/**
|
/**
|
||||||
* Get shop configuration value
|
* Get shop configuration value
|
||||||
*
|
*
|
||||||
* @param string $key
|
|
||||||
* @param mixed $default
|
* @param mixed $default
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
|
@ -936,8 +914,6 @@ class ShopService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default shop currency
|
* Get default shop currency
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function currency(): string
|
public function currency(): string
|
||||||
{
|
{
|
||||||
|
|
@ -962,7 +938,7 @@ class ShopService
|
||||||
/**
|
/**
|
||||||
* The configured {@see StripeTransaction} model class.
|
* The configured {@see StripeTransaction} model class.
|
||||||
*
|
*
|
||||||
* @return class-string<\Illuminate\Database\Eloquent\Model>
|
* @return class-string<Model>
|
||||||
*/
|
*/
|
||||||
protected function ledgerModel(): string
|
protected function ledgerModel(): string
|
||||||
{
|
{
|
||||||
|
|
@ -1233,8 +1209,8 @@ class ShopService
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
\Stripe\Stripe::setApiKey($secret);
|
Stripe::setApiKey($secret);
|
||||||
$charge = \Stripe\Charge::retrieve([
|
$charge = Charge::retrieve([
|
||||||
'id' => $chargeId,
|
'id' => $chargeId,
|
||||||
'expand' => ['balance_transaction', 'refunds.data.balance_transaction'],
|
'expand' => ['balance_transaction', 'refunds.data.balance_transaction'],
|
||||||
]);
|
]);
|
||||||
|
|
@ -1254,7 +1230,7 @@ class ShopService
|
||||||
|
|
||||||
return $count;
|
return $count;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Illuminate\Support\Facades\Log::warning('[shop:ledger] syncLedgerForCharge failed', [
|
Log::warning('[shop:ledger] syncLedgerForCharge failed', [
|
||||||
'charge' => $chargeId,
|
'charge' => $chargeId,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue