Compare commits

..

No commits in common. "master" and "testbranch" have entirely different histories.

36 changed files with 344 additions and 815 deletions

38
.github/kaizen.md vendored
View File

@ -50,41 +50,3 @@ if ($singlePrice !== null) {
- `src/Models/Cart.php` - removed pricing strategy comparison - `src/Models/Cart.php` - removed pricing strategy comparison
**Key Learning:** ALWAYS verify understanding of business logic before implementing. Pool pricing strategy is about allocation order, not price comparison. **Key Learning:** ALWAYS verify understanding of business logic before implementing. Pool pricing strategy is about allocation order, not price comparison.
### 2026-01-05: Cart Item Price/Currency Fixes
**Issues Fixed:**
1. Pool singles bookings should show `unit_amount` when added (not 0), even without dates
2. Bug: Date range adjustment was showing wrong price (5000 instead of 1755) when singles had no price
3. Added `currency` column to cart_items table to store currency from selected price
4. Removed obsolete `allocated_single_item_name` from meta (replaced by `product_id` column)
**Root Cause of Price Bug:**
- `updateDates()` was calling `$allocatedSingle->defaultPrice()->first()` instead of using `$this->price()->first()`
- When single has no price, `reallocatePoolItems` sets `price_id` to the pool's price model
- `updateDates()` was ignoring this and going back to the single's (non-existent) price
**Fix Applied:**
```php
// In CartItem::updateDates()
// IMPORTANT: Use the price_id relationship first, as it was set by reallocatePoolItems
$priceModel = $this->price_id ? $this->price()->first() : null;
if ($priceModel) {
$pricePerDay = $priceModel->getCurrentPrice(...);
} else {
// Fallback: Get price from the allocated single, with fallback to pool price
...
}
```
**New CartItem Fields:**
- `currency`: Currency from the selected price model (e.g., 'USD', 'EUR')
**Removed:**
- `meta->allocated_single_item_name` - use `$cartItem->product->name` instead via the `product_id` relationship
**Files Modified:**
- `src/Models/CartItem.php` - added currency, fixed updateDates price resolution
- `src/Models/Cart.php` - added currency to addToCart and reallocatePoolItems
- `src/Traits/MayBePoolProduct.php` - added currency to getNextAvailablePoolItemWithPrice return
- `database/migrations/create_blax_shop_tables.php.stub` - added currency column

13
.github/models.md vendored
View File

@ -40,17 +40,8 @@ The goal of this file is to not miss any important model traits, relationships,
### CartItem ### CartItem
- An item within a Cart. - An item within a Cart.
- Links a `Product` (purchasable) and a specific `ProductPrice`. - Links a `Product` and a specific `ProductPrice`.
- **Key Attributes**: - **Key Attributes**: `quantity`, `dates` (for bookings), `configuration`.
- `purchasable_id`, `purchasable_type`: The product being purchased
- `product_id`: For pool items, the allocated single item; otherwise null
- `price_id`: The selected price model
- `currency`: Currency from the selected price
- `quantity`: Number of items
- `unit_amount`: Base price per unit (per day for bookings)
- `price`: Calculated price (unit_amount × days for bookings, same as unit_amount for simple)
- `subtotal`: Total (price × quantity)
- `from`, `until`: Booking date range (for booking products)
## Order Management ## Order Management
### Order ### Order

View File

@ -234,28 +234,4 @@ class OrderFactory extends Factory
'payment_reference' => $reference ?? 'pi_' . $this->faker->regexify('[A-Za-z0-9]{24}'), 'payment_reference' => $reference ?? 'pi_' . $this->faker->regexify('[A-Za-z0-9]{24}'),
]); ]);
} }
/**
* Set booking date range.
*/
public function withDateRange(
\DateTimeInterface $from,
\DateTimeInterface $until
): static {
return $this->state([
'from' => $from,
'until' => $until,
]);
}
/**
* Set as a booking order with default date range.
*/
public function booking(): static
{
return $this->state([
'from' => now()->addDay(),
'until' => now()->addDays(3),
]);
}
} }

View File

@ -82,11 +82,11 @@ class ProductFactory extends Factory
public function withPrices( public function withPrices(
int $count = 1, int $count = 1,
null|int $unit_amount = null, null|float $unit_amount = null,
null|int $sale_unit_amount = null null|float $sale_unit_amount = null
): static { ): static {
return $this->afterCreating(function (Product $product) use ($count, $unit_amount, $sale_unit_amount) { return $this->afterCreating(function (Product $product) use ($count, $unit_amount, $sale_unit_amount) {
// All prices are in cents (smallest currency unit) // Use realistic price range if not specified
$priceAmount = $unit_amount ?? $this->faker->randomElement([ $priceAmount = $unit_amount ?? $this->faker->randomElement([
1999, // $19.99 1999, // $19.99
2999, // $29.99 2999, // $29.99

View File

@ -285,7 +285,6 @@ return new class extends Migration
$table->foreignUuid('purchase_id')->nullable()->constrained(config('shop.tables.product_purchases', 'product_purchases'))->nullOnDelete(); $table->foreignUuid('purchase_id')->nullable()->constrained(config('shop.tables.product_purchases', 'product_purchases'))->nullOnDelete();
$table->foreignUuid('price_id')->nullable()->constrained(config('shop.tables.product_prices', 'product_prices'))->nullOnDelete(); $table->foreignUuid('price_id')->nullable()->constrained(config('shop.tables.product_prices', 'product_prices'))->nullOnDelete();
$table->integer('quantity')->default(1); $table->integer('quantity')->default(1);
$table->string('currency', 3)->nullable(); // Currency from the selected price
$table->integer('price')->nullable(); // Stored in cents, null = unavailable $table->integer('price')->nullable(); // Stored in cents, null = unavailable
$table->integer('regular_price')->nullable(); // Stored in cents $table->integer('regular_price')->nullable(); // Stored in cents
$table->integer('unit_amount')->nullable(); // Base unit price for 1 quantity, 1 day (in cents) $table->integer('unit_amount')->nullable(); // Base unit price for 1 quantity, 1 day (in cents)
@ -410,10 +409,6 @@ return new class extends Migration
$table->string('ip_address')->nullable(); $table->string('ip_address')->nullable();
$table->text('user_agent')->nullable(); $table->text('user_agent')->nullable();
// Booking date range (for booking-related orders)
$table->timestamp('from')->nullable();
$table->timestamp('until')->nullable();
// Important timestamps // Important timestamps
$table->timestamp('completed_at')->nullable(); $table->timestamp('completed_at')->nullable();
$table->timestamp('paid_at')->nullable(); $table->timestamp('paid_at')->nullable();

View File

@ -305,14 +305,13 @@ $cartItem = $cart->addToCart($parkingPool, $quantity = 2, [], $from, $until);
- Claims 1 unit from each: `$spot->claimStock(1, $cartItem, $from, $until)` - Claims 1 unit from each: `$spot->claimStock(1, $cartItem, $from, $until)`
3. **Store Claimed Items** 3. **Store Claimed Items**
- Cart item's `product_id` column stores which single item was allocated - Cart item metadata stores which single items were claimed
- Each cart item is linked to one specific single item - Metadata: `claimed_single_items: [spot1_id, spot2_id]`
4. **Calculate Price** 4. **Calculate Price**
- Gets price from available single items (using pricing strategy) - Gets price from available single items (using pricing strategy)
- If single has no price, falls back to pool's price - Multiplies by number of days
- Multiplies by number of days for booking products - Stores in cart item
- Stores in cart item (unit_amount, price, subtotal)
### Manual Stock Operations ### Manual Stock Operations
@ -400,15 +399,11 @@ $cartItem = $cart->addToCart($parkingPool, $quantity = 1, [], $from, $until);
// Cart item properties: // Cart item properties:
// - purchasable_id: Pool Product ID // - purchasable_id: Pool Product ID
// - purchasable_type: Product::class // - purchasable_type: Product::class
// - product_id: Allocated Single Item ID // - product_id: Allocated Single Item ID (NEW!)
// - price_id: Price used (from single or pool fallback)
// - currency: Currency from the selected price
// - quantity: 1 // - quantity: 1
// - from: 2025-01-15 // - from: 2025-01-15
// - until: 2025-01-17 // - until: 2025-01-17
// - unit_amount: Price per day (in cents) // - price: (unit_amount × 2 days)
// - price: unit_amount × days (calculated booking price)
// - subtotal: price × quantity
``` ```
### Product ID Column ### Product ID Column
@ -692,12 +687,13 @@ $price = $pool->getLowestAvailablePoolPrice($from, $until);
### Single Items Not Released After Cart Deletion ### Single Items Not Released After Cart Deletion
**Cause:** Cart item's `product_id` not properly tracking claimed single **Cause:** Metadata not properly storing claimed items
**Solution:** **Solution:**
```php ```php
// Check the cart item's product_id // Ensure cart item has metadata
$allocatedSingle = $cartItem->product; $meta = $cartItem->getMeta();
$claimedItems = $meta->claimed_single_items ?? [];
// Manually release if needed // Manually release if needed
$pool->releasePoolStock($cartItem); $pool->releasePoolStock($cartItem);

View File

@ -156,18 +156,16 @@ class StripeWebhookController
} }
// Record payment on the order // Record payment on the order
// Stripe provides amounts in cents, which matches our storage format $amountPaid = (int) (($session->amount_total ?? 0) / 100);
$amountPaid = (int) ($session->amount_total ?? 0);
$currency = strtoupper($session->currency ?? $order->currency ?? 'USD'); $currency = strtoupper($session->currency ?? $order->currency ?? 'USD');
// recordPayment(int $amount, ?string $reference, ?string $method, ?string $provider) // recordPayment(int $amount, ?string $reference, ?string $method, ?string $provider)
$order->recordPayment($amountPaid, $session->payment_intent, 'stripe', 'stripe'); $order->recordPayment($amountPaid, $session->payment_intent, 'stripe', 'stripe');
// Add a detailed note (customer-visible) // Add a detailed note
$order->addNote( $order->addNote(
"Payment of " . Order::formatMoney($amountPaid, $currency) . " received", "Payment of " . Order::formatMoney($amountPaid, $currency) . " received via Stripe checkout (Session: {$session->id})",
OrderNote::TYPE_PAYMENT, OrderNote::TYPE_PAYMENT
true
); );
// Mark order as processing if payment is successful // Mark order as processing if payment is successful
@ -203,11 +201,10 @@ class StripeWebhookController
$order = $cart->order; $order = $cart->order;
if ($order && $order->status->canTransitionTo(OrderStatus::FAILED)) { if ($order && $order->status->canTransitionTo(OrderStatus::FAILED)) {
$order->update(['status' => OrderStatus::FAILED]); $order->update(['status' => OrderStatus::FAILED]);
// Internal note - payment failure details should not be shown to customer // addNote(string $content, string $type, bool $isCustomerNote, ?string $authorType, ?string $authorId)
$order->addNote( $order->addNote(
"Payment failed via Stripe checkout (Session: {$session->id})", "Payment failed via Stripe checkout (Session: {$session->id})",
OrderNote::TYPE_PAYMENT, OrderNote::TYPE_PAYMENT
false
); );
} }
} }
@ -233,11 +230,9 @@ class StripeWebhookController
// Add note to order if it exists // Add note to order if it exists
$order = $cart->order; $order = $cart->order;
if ($order) { if ($order) {
// Internal note - session expiry is a technical detail
$order->addNote( $order->addNote(
"Stripe checkout session expired (Session: {$session->id})", "Stripe checkout session expired (Session: {$session->id})",
OrderNote::TYPE_SYSTEM, OrderNote::TYPE_SYSTEM
false
); );
} }
} }

View File

@ -686,7 +686,6 @@ class Cart extends Model
'single' => $single, 'single' => $single,
'price' => $price, 'price' => $price,
'price_id' => $priceModel?->id, 'price_id' => $priceModel?->id,
'currency' => $priceModel?->currency,
'available' => $effectiveAvailable, 'available' => $effectiveAvailable,
]; ];
} }
@ -708,6 +707,7 @@ class Cart extends Model
'subtotal' => null, 'subtotal' => null,
'unit_amount' => null, 'unit_amount' => null,
]); ]);
$cartItem->updateMetaKey('allocated_single_item_name', null);
} }
continue; continue;
} }
@ -751,10 +751,12 @@ class Cart extends Model
if ($singleInfo['price_id'] && $singleInfo['price_id'] !== $cartItem->price_id) { if ($singleInfo['price_id'] && $singleInfo['price_id'] !== $cartItem->price_id) {
$updates['price_id'] = $singleInfo['price_id']; $updates['price_id'] = $singleInfo['price_id'];
} }
if ($singleInfo['currency']) {
$updates['currency'] = $singleInfo['currency'];
}
$cartItem->update($updates); $cartItem->update($updates);
$cartItem->updateMetaKey('allocated_single_item_name', $single->name);
// Legacy: update price_id if changed (now handled in the update above)
if (false) {
}
// Track usage // Track usage
$singleUsage[$single->id] = $usedFromSingle + $neededQty; $singleUsage[$single->id] = $usedFromSingle + $neededQty;
@ -793,19 +795,21 @@ class Cart extends Model
if ($singleInfo['price_id'] && $singleInfo['price_id'] !== $cartItem->price_id) { if ($singleInfo['price_id'] && $singleInfo['price_id'] !== $cartItem->price_id) {
$updates['price_id'] = $singleInfo['price_id']; $updates['price_id'] = $singleInfo['price_id'];
} }
if ($singleInfo['currency']) {
$updates['currency'] = $singleInfo['currency'];
}
$cartItem->update($updates); $cartItem->update($updates);
$cartItem->refresh(); // Ensure model reflects database state $cartItem->refresh(); // Ensure model reflects database state
$cartItem->updateMetaKey('allocated_single_item_name', $single->name);
$firstAllocation = false; $firstAllocation = false;
} else { } else {
// Create a new cart item for the additional quantity // Create a new cart item for the additional quantity
// Use the price info from singleInfo (already calculated with pool fallback) // Get price from the single
$singlePrice = $singleInfo['price']; $priceModel = $single->defaultPrice()->first();
$priceId = $singleInfo['price_id']; $singlePrice = $priceModel?->getCurrentPrice($single->isOnSale());
$currency = $singleInfo['currency'];
if ($singlePrice === null && $poolProduct->hasPrice()) {
$priceModel = $poolProduct->defaultPrice()->first();
$singlePrice = $priceModel?->getCurrentPrice($poolProduct->isOnSale());
}
$days = $this->calculateBookingDays($from, $until); $days = $this->calculateBookingDays($from, $until);
$pricePerUnit = (int) round($singlePrice * $days); $pricePerUnit = (int) round($singlePrice * $days);
@ -814,9 +818,8 @@ class Cart extends Model
'purchasable_id' => $cartItem->purchasable_id, 'purchasable_id' => $cartItem->purchasable_id,
'purchasable_type' => $cartItem->purchasable_type, 'purchasable_type' => $cartItem->purchasable_type,
'product_id' => $single->id, 'product_id' => $single->id,
'price_id' => $priceId, 'price_id' => $priceModel?->id,
'quantity' => $qtyToAllocate, 'quantity' => $qtyToAllocate,
'currency' => $currency,
'price' => $pricePerUnit, 'price' => $pricePerUnit,
'regular_price' => $pricePerUnit, 'regular_price' => $pricePerUnit,
'unit_amount' => (int) round($singlePrice), 'unit_amount' => (int) round($singlePrice),
@ -825,6 +828,8 @@ class Cart extends Model
'from' => $from, 'from' => $from,
'until' => $until, 'until' => $until,
]); ]);
$newCartItem->updateMetaKey('allocated_single_item_name', $single->name);
} }
$singleUsage[$single->id] = $usedFromSingle + $qtyToAllocate; $singleUsage[$single->id] = $usedFromSingle + $qtyToAllocate;
@ -842,6 +847,7 @@ class Cart extends Model
'subtotal' => null, 'subtotal' => null,
'unit_amount' => null, 'unit_amount' => null,
]); ]);
$cartItem->updateMetaKey('allocated_single_item_name', null);
} else { } else {
// Partial allocation - the cart item was already updated with what we could allocate // Partial allocation - the cart item was already updated with what we could allocate
// The remaining quantity is lost (over-capacity) // The remaining quantity is lost (over-capacity)
@ -1257,7 +1263,7 @@ class Cart extends Model
$poolPriceId = $priceModel?->id; $poolPriceId = $priceModel?->id;
// Still try to find a single item for allocation even with pool's direct price // Still try to find a single item for allocation even with pool's direct price
// This ensures product_id is always set for pool items // This ensures allocated_single_item_name is always set for pool items
if (!$poolSingleItem) { if (!$poolSingleItem) {
$singleItems = $cartable->singleProducts; $singleItems = $cartable->singleProducts;
foreach ($singleItems as $single) { foreach ($singleItems as $single) {
@ -1332,26 +1338,20 @@ class Cart extends Model
return $existingItem->fresh(); return $existingItem->fresh();
} }
// Determine price_id and currency for the cart item // Determine price_id for the cart item
$priceId = null; $priceId = null;
$currency = null;
if ($cartable instanceof Product) { if ($cartable instanceof Product) {
// For pool products, use the single item's price_id and currency // For pool products, use the single item's price_id
if ($is_pool && $poolPriceId) { if ($is_pool && $poolPriceId) {
$priceId = $poolPriceId; $priceId = $poolPriceId;
// Get currency from poolItemData if available
$poolItemData = $cartable->getNextAvailablePoolItemWithPrice($this, null, $from, $until);
$currency = $poolItemData['currency'] ?? null;
} else { } else {
// Get the default price for the product // Get the default price for the product
$defaultPrice = $cartable->defaultPrice()->first(); $defaultPrice = $cartable->defaultPrice()->first();
$priceId = $defaultPrice?->id; $priceId = $defaultPrice?->id;
$currency = $defaultPrice?->currency;
} }
} elseif ($cartable instanceof \Blax\Shop\Models\ProductPrice) { } elseif ($cartable instanceof \Blax\Shop\Models\ProductPrice) {
// If adding a ProductPrice directly, use its ID and currency // If adding a ProductPrice directly, use its ID
$priceId = $cartable->id; $priceId = $cartable->id;
$currency = $cartable->currency;
} }
// Create new cart item // Create new cart item
@ -1361,7 +1361,6 @@ class Cart extends Model
'product_id' => ($cartable instanceof Product && $cartable->isPool() && $poolSingleItem) ? $poolSingleItem->id : null, 'product_id' => ($cartable instanceof Product && $cartable->isPool() && $poolSingleItem) ? $poolSingleItem->id : null,
'price_id' => $priceId, 'price_id' => $priceId,
'quantity' => $quantity, 'quantity' => $quantity,
'currency' => $currency,
'price' => $pricePerUnit, // Price per unit for the period 'price' => $pricePerUnit, // Price per unit for the period
'regular_price' => $regularPricePerUnit, 'regular_price' => $regularPricePerUnit,
'unit_amount' => $unitAmount, // Base price for 1 quantity, 1 day (in cents) 'unit_amount' => $unitAmount, // Base price for 1 quantity, 1 day (in cents)
@ -1371,6 +1370,11 @@ class Cart extends Model
'until' => ($is_booking) ? $until : null, 'until' => ($is_booking) ? $until : null,
]); ]);
// For pool products, store the single item name in meta for display purposes
if ($cartable instanceof Product && $cartable->isPool() && $poolSingleItem) {
$cartItem->updateMetaKey('allocated_single_item_name', $poolSingleItem->name);
}
// Touch activity timestamp // Touch activity timestamp
$this->touchActivity(); $this->touchActivity();
@ -2031,7 +2035,7 @@ class Cart extends Model
// Build line item using price_data for dynamic pricing // Build line item using price_data for dynamic pricing
$lineItem = [ $lineItem = [
'price_data' => [ 'price_data' => [
'currency' => $item->price->currency ?? strtoupper($this->currency), 'currency' => config('shop.currency', 'usd'),
'product_data' => [ 'product_data' => [
'name' => $productName, 'name' => $productName,
...($description ? ['description' => $description] : []), ...($description ? ['description' => $description] : []),
@ -2058,7 +2062,6 @@ class Cart extends Model
// Prepare session parameters // Prepare session parameters
$sessionParams = [ $sessionParams = [
'payment_method_types' => ['card'], 'payment_method_types' => ['card'],
'currency' => strtoupper($this->currency),
'line_items' => $lineItems, 'line_items' => $lineItems,
'mode' => 'payment', 'mode' => 'payment',
'success_url' => $success_url, 'success_url' => $success_url,

View File

@ -21,7 +21,6 @@ class CartItem extends Model
'product_id', 'product_id',
'price_id', 'price_id',
'quantity', 'quantity',
'currency',
'price', 'price',
'regular_price', 'regular_price',
'unit_amount', 'unit_amount',
@ -35,7 +34,6 @@ class CartItem extends Model
protected $casts = [ protected $casts = [
'quantity' => 'integer', 'quantity' => 'integer',
'currency' => 'string',
'price' => 'integer', 'price' => 'integer',
'regular_price' => 'integer', 'regular_price' => 'integer',
'unit_amount' => 'integer', 'unit_amount' => 'integer',
@ -505,37 +503,22 @@ class CartItem extends Model
// This ensures consistency when reallocatePoolItems has already assigned a specific single // This ensures consistency when reallocatePoolItems has already assigned a specific single
// The product_id column stores the actual single product being purchased // The product_id column stores the actual single product being purchased
$allocatedSingleItemId = $this->product_id; $allocatedSingleItemId = $this->product_id;
$currency = null;
if ($product->isPool() && $allocatedSingleItemId) { if ($product->isPool() && $allocatedSingleItemId) {
// Get the allocated single item from the product_id column // Get the allocated single item from the product_id column
$allocatedSingle = $this->product; $allocatedSingle = $this->product;
if ($allocatedSingle) { if ($allocatedSingle) {
// IMPORTANT: Use the price_id relationship first, as it was set by reallocatePoolItems // Get price from the allocated single, with fallback to pool price
// to the correct price (either single's price or pool's fallback price).
// Only fall back to defaultPrice() if price_id is not set.
$priceModel = $this->price_id ? $this->price()->first() : null;
if ($priceModel) {
// Use the price model from price_id relationship
$pricePerDay = $priceModel->getCurrentPrice($allocatedSingle->isOnSale() || $product->isOnSale());
$regularPricePerDay = $priceModel->getCurrentPrice(false) ?? $pricePerDay;
$currency = $priceModel->currency;
} else {
// Fallback: Get price from the allocated single, with fallback to pool price
$priceModel = $allocatedSingle->defaultPrice()->first(); $priceModel = $allocatedSingle->defaultPrice()->first();
$pricePerDay = $priceModel?->getCurrentPrice($allocatedSingle->isOnSale()); $pricePerDay = $priceModel?->getCurrentPrice($allocatedSingle->isOnSale());
$regularPricePerDay = $priceModel?->getCurrentPrice(false) ?? $pricePerDay; $regularPricePerDay = $priceModel?->getCurrentPrice(false) ?? $pricePerDay;
$currency = $priceModel?->currency;
// Fallback to pool price if single has no price // Fallback to pool price if single has no price
if ($pricePerDay === null && $product->hasPrice()) { if ($pricePerDay === null && $product->hasPrice()) {
$poolPriceModel = $product->defaultPrice()->first(); $poolPriceModel = $product->defaultPrice()->first();
$pricePerDay = $poolPriceModel?->getCurrentPrice($product->isOnSale()); $pricePerDay = $poolPriceModel?->getCurrentPrice($product->isOnSale());
$regularPricePerDay = $poolPriceModel?->getCurrentPrice(false) ?? $pricePerDay; $regularPricePerDay = $poolPriceModel?->getCurrentPrice(false) ?? $pricePerDay;
$currency = $poolPriceModel?->currency;
}
} }
} else { } else {
// Allocated single not found - this is an error state, mark as unavailable // Allocated single not found - this is an error state, mark as unavailable
@ -555,10 +538,6 @@ class CartItem extends Model
// Pass cart item ID to exclude this item from usage calculation // Pass cart item ID to exclude this item from usage calculation
$pricePerDay = $product->getCurrentPrice(null, $this->cart, $from, $until, $this->id); $pricePerDay = $product->getCurrentPrice(null, $this->cart, $from, $until, $this->id);
$regularPricePerDay = $product->getCurrentPrice(false, $this->cart, $from, $until, $this->id) ?? $pricePerDay; $regularPricePerDay = $product->getCurrentPrice(false, $this->cart, $from, $until, $this->id) ?? $pricePerDay;
// Get currency from the price model
$priceModel = $product->defaultPrice()->first();
$currency = $priceModel?->currency;
} }
// If no price found, mark as unavailable // If no price found, mark as unavailable
@ -584,7 +563,6 @@ class CartItem extends Model
$this->update([ $this->update([
'from' => $from, 'from' => $from,
'until' => $until, 'until' => $until,
'currency' => $currency,
'price' => $pricePerUnit, 'price' => $pricePerUnit,
'regular_price' => $regularPricePerUnit, 'regular_price' => $regularPricePerUnit,
'unit_amount' => $unitAmount, 'unit_amount' => $unitAmount,

View File

@ -47,8 +47,6 @@ class Order extends Model
'internal_note', 'internal_note',
'ip_address', 'ip_address',
'user_agent', 'user_agent',
'from',
'until',
'completed_at', 'completed_at',
'paid_at', 'paid_at',
'shipped_at', 'shipped_at',
@ -70,8 +68,6 @@ class Order extends Model
'billing_address' => 'object', 'billing_address' => 'object',
'shipping_address' => 'object', 'shipping_address' => 'object',
'meta' => 'object', 'meta' => 'object',
'from' => 'datetime',
'until' => 'datetime',
'completed_at' => 'datetime', 'completed_at' => 'datetime',
'paid_at' => 'datetime', 'paid_at' => 'datetime',
'shipped_at' => 'datetime', 'shipped_at' => 'datetime',
@ -147,11 +143,10 @@ class Order extends Model
$difference = $newPaid - $oldPaid; $difference = $newPaid - $oldPaid;
if ($difference > 0) { if ($difference > 0) {
$currency = $order->currency ?? config('shop.currency', 'USD');
$order->addNote( $order->addNote(
"Payment received: " . static::formatMoney($difference, $currency), "Payment received: " . static::formatMoney($difference, $order->currency),
'payment', 'payment',
true false
); );
// Mark as paid if fully paid // Mark as paid if fully paid
@ -442,12 +437,10 @@ class Order extends Model
$this->amount_refunded = ($this->amount_refunded ?? 0) + $amount; $this->amount_refunded = ($this->amount_refunded ?? 0) + $amount;
$this->save(); $this->save();
$currency = $this->currency ?? config('shop.currency', 'USD');
$this->addNote( $this->addNote(
"Refund processed: " . static::formatMoney($amount, $currency) . "Refund processed: " . static::formatMoney($amount, $this->currency) .
($reason ? " - Reason: {$reason}" : ''), ($reason ? " - Reason: {$reason}" : ''),
'refund', 'refund'
true
); );
// If fully refunded, update status // If fully refunded, update status
@ -465,27 +458,20 @@ class Order extends Model
/** /**
* Add a note to the order. * Add a note to the order.
*
* @param string $content The note content
* @param string $type The note type (note, status_change, payment, etc.)
* @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 array|object|null $meta Additional metadata
*/ */
public function addNote( public function addNote(
string $content, string $content,
string $type = 'note', string $type = 'note',
bool $isCustomerNote = false, bool $isCustomerNote = false,
$author = null, ?string $authorType = null,
$meta = null ?string $authorId = null
): OrderNote { ): OrderNote {
return $this->notes()->create([ return $this->notes()->create([
'content' => $content, 'content' => $content,
'type' => $type, 'type' => $type,
'is_customer_note' => $isCustomerNote, 'is_customer_note' => $isCustomerNote,
'author_type' => $author ? get_class($author) : null, 'author_type' => $authorType,
'author_id' => $author?->getKey(), 'author_id' => $authorId,
'meta' => $meta,
]); ]);
} }
@ -569,56 +555,6 @@ class Order extends Model
]); ]);
} }
/**
* Scope to get final/finished orders (completed, cancelled, refunded, failed, delivered).
* Uses OrderStatus::isFinal() to determine which statuses are final.
*/
public function scopeFinal($query)
{
$finalStatuses = array_map(
fn(OrderStatus $status) => $status->value,
array_filter(OrderStatus::cases(), fn(OrderStatus $status) => $status->isFinal())
);
return $query->whereIn('status', $finalStatuses);
}
/**
* Alias for scopeFinal - get finished orders.
*/
public function scopeFinished($query)
{
return $this->scopeFinal($query);
}
/**
* Scope to get orders requiring payment (pending status).
* Uses OrderStatus::requiresPayment() to determine which statuses require payment.
*/
public function scopeRequiresPayment($query)
{
$requiresPaymentStatuses = array_map(
fn(OrderStatus $status) => $status->value,
array_filter(OrderStatus::cases(), fn(OrderStatus $status) => $status->requiresPayment())
);
return $query->whereIn('status', $requiresPaymentStatuses);
}
/**
* Scope to get orders with a paid status (processing, shipped, delivered, etc.).
* Uses OrderStatus::isPaid() - this is different from scopePaid() which checks amounts.
*/
public function scopeStatusPaid($query)
{
$paidStatuses = array_map(
fn(OrderStatus $status) => $status->value,
array_filter(OrderStatus::cases(), fn(OrderStatus $status) => $status->isPaid())
);
return $query->whereIn('status', $paidStatuses);
}
/** /**
* Scope to get completed orders. * Scope to get completed orders.
*/ */
@ -628,7 +564,7 @@ class Order extends Model
} }
/** /**
* Scope to get paid orders (by amount). * Scope to get paid orders.
*/ */
public function scopePaid($query) public function scopePaid($query)
{ {
@ -636,7 +572,7 @@ class Order extends Model
} }
/** /**
* Scope to get unpaid orders (by amount). * Scope to get unpaid orders.
*/ */
public function scopeUnpaid($query) public function scopeUnpaid($query)
{ {
@ -898,19 +834,17 @@ class Order extends Model
'customer_type' => $cart->customer_type, 'customer_type' => $cart->customer_type,
'customer_id' => $cart->customer_id, 'customer_id' => $cart->customer_id,
'currency' => $cart->currency ?? config('shop.currency', 'USD'), 'currency' => $cart->currency ?? config('shop.currency', 'USD'),
'amount_subtotal' => (int) $cart->getTotal(), 'amount_subtotal' => (int) $cart->getTotal() * 100,
'amount_discount' => 0, // TODO: Calculate from cart discounts 'amount_discount' => 0, // TODO: Calculate from cart discounts
'amount_shipping' => 0, 'amount_shipping' => 0,
'amount_tax' => 0, 'amount_tax' => 0,
'amount_total' => (int) $cart->getTotal(), 'amount_total' => (int) $cart->getTotal() * 100,
'amount_paid' => 0, 'amount_paid' => 0,
'amount_refunded' => 0, 'amount_refunded' => 0,
'from' => $cart->from,
'until' => $cart->until,
'status' => OrderStatus::PENDING, 'status' => OrderStatus::PENDING,
]); ]);
$order->addNote('Order created from cart checkout', 'system', true); $order->addNote('Order created from cart checkout', 'system', false);
return $order; return $order;
} }

View File

@ -744,7 +744,7 @@ trait MayBePoolProduct
* @param \DateTimeInterface|null $from Start date for availability check * @param \DateTimeInterface|null $from Start date for availability check
* @param \DateTimeInterface|null $until End date for availability check * @param \DateTimeInterface|null $until End date for availability check
* @param string|int|null $excludeCartItemId Cart item ID to exclude from usage calculation (for date updates) * @param string|int|null $excludeCartItemId Cart item ID to exclude from usage calculation (for date updates)
* @return array|null ['price' => float, 'item' => Product, 'price_id' => string|null, 'currency' => string|null] * @return array|null ['price' => float, 'item' => Product, 'price_id' => string|null]
*/ */
public function getNextAvailablePoolItemWithPrice( public function getNextAvailablePoolItemWithPrice(
\Blax\Shop\Models\Cart $cart, \Blax\Shop\Models\Cart $cart,
@ -856,7 +856,6 @@ trait MayBePoolProduct
'quantity' => $availableFromThisItem, 'quantity' => $availableFromThisItem,
'item' => $item, 'item' => $item,
'price_id' => $priceModel?->id, 'price_id' => $priceModel?->id,
'currency' => $priceModel?->currency,
]; ];
} }
} }
@ -896,7 +895,6 @@ trait MayBePoolProduct
'price' => $averagePrice, 'price' => $averagePrice,
'item' => $availableItems[0]['item'], 'item' => $availableItems[0]['item'],
'price_id' => $availableItems[0]['price_id'], 'price_id' => $availableItems[0]['price_id'],
'currency' => $availableItems[0]['currency'],
]; ];
} }
@ -914,7 +912,6 @@ trait MayBePoolProduct
'price' => $availableItems[0]['price'], 'price' => $availableItems[0]['price'],
'item' => $availableItems[0]['item'], 'item' => $availableItems[0]['item'],
'price_id' => $availableItems[0]['price_id'], 'price_id' => $availableItems[0]['price_id'],
'currency' => $availableItems[0]['currency'],
]; ];
} }

View File

@ -516,7 +516,7 @@ class BookingPerMinutePricingTest extends TestCase
$single_product = Product::factory() $single_product = Product::factory()
->withStocks(5) ->withStocks(5)
->withPrices(1, 5000) // 5000 cents = $50.00 ->withPrices(1, 5000) // $50.00
->create([ ->create([
'name' => 'Wine Bottle', 'name' => 'Wine Bottle',
'slug' => 'wine-bottle', 'slug' => 'wine-bottle',

View File

@ -156,7 +156,7 @@ class CartFacadeTest extends TestCase
$total = Cart::total(); $total = Cart::total();
$this->assertEquals(200, $total); $this->assertEquals(200.00, $total);
} }
#[Test] #[Test]
@ -219,7 +219,7 @@ class CartFacadeTest extends TestCase
$unpaid = Cart::unpaidAmount(); $unpaid = Cart::unpaidAmount();
$this->assertEquals(200, $unpaid); $this->assertEquals(200.00, $unpaid);
} }
#[Test] #[Test]
@ -230,7 +230,7 @@ class CartFacadeTest extends TestCase
$paid = Cart::paidAmount(); $paid = Cart::paidAmount();
$this->assertEquals(0, $paid); $this->assertEquals(0.00, $paid);
} }
#[Test] #[Test]
@ -292,7 +292,7 @@ class CartFacadeTest extends TestCase
Cart::add($p2, quantity: 1); // 75 Cart::add($p2, quantity: 1); // 75
Cart::add($p3, quantity: 4); // 100 Cart::add($p3, quantity: 4); // 100
$this->assertEquals(275, Cart::total()); $this->assertEquals(275.00, Cart::total());
} }
#[Test] #[Test]
@ -300,10 +300,10 @@ class CartFacadeTest extends TestCase
{ {
$product = Product::factory()->withStocks(50)->withPrices(1, 100)->create(); $product = Product::factory()->withStocks(50)->withPrices(1, 100)->create();
Cart::add($product, quantity: 5); Cart::add($product, quantity: 5);
$this->assertEquals(500, Cart::total()); $this->assertEquals(500.00, Cart::total());
Cart::remove($product, quantity: 2); Cart::remove($product, quantity: 2);
$this->assertEquals(300, Cart::total()); $this->assertEquals(300.00, Cart::total());
} }
} }

View File

@ -20,7 +20,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_has_is_booking_attribute_for_booking_products() public function cart_item_has_is_booking_attribute_for_booking_products()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$cart = Cart::create(); $cart = Cart::create();
@ -33,7 +33,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_has_is_booking_false_for_regular_products() public function cart_item_has_is_booking_false_for_regular_products()
{ {
$regularProduct = Product::factory() $regularProduct = Product::factory()
->withPrices(unit_amount: 5000) ->withPrices(unit_amount: 50.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -46,7 +46,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_is_booking_works_via_price_id() public function cart_item_is_booking_works_via_price_id()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$cart = Cart::create(); $cart = Cart::create();
@ -64,11 +64,11 @@ class CartItemAttributesTest extends TestCase
public function cart_is_full_booking_is_true_when_all_items_are_bookings() public function cart_is_full_booking_is_true_when_all_items_are_bookings()
{ {
$booking1 = Product::factory() $booking1 = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$booking2 = Product::factory() $booking2 = Product::factory()
->withPrices(unit_amount: 15000) ->withPrices(unit_amount: 150.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$cart = Cart::create(); $cart = Cart::create();
@ -82,11 +82,11 @@ class CartItemAttributesTest extends TestCase
public function cart_is_full_booking_is_false_when_mixed_products() public function cart_is_full_booking_is_false_when_mixed_products()
{ {
$booking = Product::factory() $booking = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$regular = Product::factory() $regular = Product::factory()
->withPrices(unit_amount: 5000) ->withPrices(unit_amount: 50.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -108,15 +108,15 @@ class CartItemAttributesTest extends TestCase
public function cart_booking_items_returns_correct_count() public function cart_booking_items_returns_correct_count()
{ {
$booking1 = Product::factory() $booking1 = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$booking2 = Product::factory() $booking2 = Product::factory()
->withPrices(unit_amount: 15000) ->withPrices(unit_amount: 150.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$regular = Product::factory() $regular = Product::factory()
->withPrices(unit_amount: 5000) ->withPrices(unit_amount: 50.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -131,7 +131,7 @@ class CartItemAttributesTest extends TestCase
public function cart_booking_items_returns_zero_when_no_bookings() public function cart_booking_items_returns_zero_when_no_bookings()
{ {
$regular = Product::factory() $regular = Product::factory()
->withPrices(unit_amount: 5000) ->withPrices(unit_amount: 50.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -144,7 +144,7 @@ class CartItemAttributesTest extends TestCase
public function price_id_is_automatically_assigned_when_adding_product_to_cart() public function price_id_is_automatically_assigned_when_adding_product_to_cart()
{ {
$product = Product::factory() $product = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(); ->create();
$cart = Cart::create(); $cart = Cart::create();
@ -244,7 +244,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_is_ready_to_checkout_is_true_for_regular_products() public function cart_item_is_ready_to_checkout_is_true_for_regular_products()
{ {
$product = Product::factory() $product = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -257,7 +257,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_is_ready_to_checkout_is_false_for_booking_without_dates() public function cart_item_is_ready_to_checkout_is_false_for_booking_without_dates()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$cart = Cart::create(); $cart = Cart::create();
@ -270,7 +270,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_is_ready_to_checkout_is_true_for_booking_with_valid_dates() public function cart_item_is_ready_to_checkout_is_true_for_booking_with_valid_dates()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) ->withStocks(quantity: 10)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -287,7 +287,7 @@ class CartItemAttributesTest extends TestCase
public function cart_item_is_ready_to_checkout_is_false_for_booking_with_invalid_date_range() public function cart_item_is_ready_to_checkout_is_false_for_booking_with_invalid_date_range()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) ->withStocks(quantity: 10)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -307,11 +307,11 @@ class CartItemAttributesTest extends TestCase
public function cart_is_ready_to_checkout_is_true_when_all_items_are_ready() public function cart_is_ready_to_checkout_is_true_when_all_items_are_ready()
{ {
$product1 = Product::factory() $product1 = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$product2 = Product::factory() $product2 = Product::factory()
->withPrices(unit_amount: 15000) ->withPrices(unit_amount: 150.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$cart = Cart::create(); $cart = Cart::create();
@ -325,11 +325,11 @@ class CartItemAttributesTest extends TestCase
public function cart_is_ready_to_checkout_is_false_when_at_least_one_item_not_ready() public function cart_is_ready_to_checkout_is_false_when_at_least_one_item_not_ready()
{ {
$regularProduct = Product::factory() $regularProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->create(['type' => ProductType::SIMPLE]); ->create(['type' => ProductType::SIMPLE]);
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 15000) ->withPrices(unit_amount: 150.00)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
$cart = Cart::create(); $cart = Cart::create();
@ -343,7 +343,7 @@ class CartItemAttributesTest extends TestCase
public function cart_allows_adding_items_without_dates_that_require_them() public function cart_allows_adding_items_without_dates_that_require_them()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) // Has stock ->withStocks(quantity: 10) // Has stock
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -362,7 +362,7 @@ class CartItemAttributesTest extends TestCase
public function update_dates_allows_setting_any_dates() public function update_dates_allows_setting_any_dates()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) // Has stock ->withStocks(quantity: 10) // Has stock
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -386,7 +386,7 @@ class CartItemAttributesTest extends TestCase
public function cart_calculates_correctly_when_dates_are_adjusted() public function cart_calculates_correctly_when_dates_are_adjusted()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) ->withStocks(quantity: 10)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -397,23 +397,23 @@ class CartItemAttributesTest extends TestCase
$cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until); $cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until);
// Initial price for 2 days // Initial price for 2 days
$this->assertEquals(20000, $cartItem->price); $this->assertEquals(200.00, $cartItem->price);
$this->assertEquals(20000, $cartItem->subtotal); $this->assertEquals(200.00, $cartItem->subtotal);
// Adjust dates to 5 days // Adjust dates to 5 days
$newUntil = Carbon::now()->addDays(6); $newUntil = Carbon::now()->addDays(6);
$cartItem->updateDates($from, $newUntil); $cartItem->updateDates($from, $newUntil);
// Price should be recalculated for 5 days // Price should be recalculated for 5 days
$this->assertEquals(50000, $cartItem->fresh()->price); $this->assertEquals(500.00, $cartItem->fresh()->price);
$this->assertEquals(50000, $cartItem->fresh()->subtotal); $this->assertEquals(500.00, $cartItem->fresh()->subtotal);
} }
#[Test] #[Test]
public function set_from_date_recalculates_pricing_when_both_dates_set() public function set_from_date_recalculates_pricing_when_both_dates_set()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) ->withStocks(quantity: 10)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -424,21 +424,21 @@ class CartItemAttributesTest extends TestCase
$cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until); $cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until);
// Initial price for 3 days // Initial price for 3 days
$this->assertEquals(30000, $cartItem->price); $this->assertEquals(300.00, $cartItem->price);
// Adjust from date to make it span more days (move 1 day earlier) // Adjust from date to make it span more days (move 1 day earlier)
$newFrom = $from->copy()->subDays(1); $newFrom = $from->copy()->subDays(1);
$cartItem->setFromDate($newFrom); $cartItem->setFromDate($newFrom);
// Price should be recalculated for 4 days // Price should be recalculated for 4 days
$this->assertEquals(40000, $cartItem->fresh()->price); $this->assertEquals(400.00, $cartItem->fresh()->price);
} }
#[Test] #[Test]
public function set_until_date_recalculates_pricing_when_both_dates_set() public function set_until_date_recalculates_pricing_when_both_dates_set()
{ {
$bookingProduct = Product::factory() $bookingProduct = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 10) ->withStocks(quantity: 10)
->create(['type' => ProductType::BOOKING]); ->create(['type' => ProductType::BOOKING]);
@ -449,21 +449,21 @@ class CartItemAttributesTest extends TestCase
$cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until); $cartItem = $cart->addToCart($bookingProduct, quantity: 1, from: $from, until: $until);
// Initial price for 2 days // Initial price for 2 days
$this->assertEquals(20000, $cartItem->price); $this->assertEquals(200.00, $cartItem->price);
// Adjust until date to make it 4 days // Adjust until date to make it 4 days
$newUntil = Carbon::now()->addDays(5); $newUntil = Carbon::now()->addDays(5);
$cartItem->setUntilDate($newUntil); $cartItem->setUntilDate($newUntil);
// Price should be recalculated for 4 days // Price should be recalculated for 4 days
$this->assertEquals(40000, $cartItem->fresh()->price); $this->assertEquals(400.00, $cartItem->fresh()->price);
} }
#[Test] #[Test]
public function is_ready_to_checkout_checks_stock_for_regular_products_with_stock_management() public function is_ready_to_checkout_checks_stock_for_regular_products_with_stock_management()
{ {
$product = Product::factory() $product = Product::factory()
->withPrices(unit_amount: 10000) ->withPrices(unit_amount: 100.00)
->withStocks(quantity: 5) ->withStocks(quantity: 5)
->create([ ->create([
'type' => ProductType::SIMPLE, 'type' => ProductType::SIMPLE,

View File

@ -129,8 +129,11 @@ class CartItemAvailabilityValidationTest extends TestCase
// - Remove allocation (product_id = null) // - Remove allocation (product_id = null)
// - Set price to null (the real indicator of unavailability) // - Set price to null (the real indicator of unavailability)
$item = $this->cart->items()->first(); $item = $this->cart->items()->first();
$meta = $item->getMeta();
unset($meta->allocated_single_item_name);
$item->update([ $item->update([
'product_id' => null, 'product_id' => null,
'meta' => json_encode($meta),
'price' => null, 'price' => null,
'subtotal' => null, 'subtotal' => null,
]); ]);

View File

@ -60,7 +60,7 @@ class CartManagementTest extends TestCase
public function it_can_update_cart_item_quantity() public function it_can_update_cart_item_quantity()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 1); $cartItem = $cart->addToCart($price, quantity: 1);
@ -73,7 +73,7 @@ class CartManagementTest extends TestCase
public function it_can_remove_items_from_cart() public function it_can_remove_items_from_cart()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create(); $product = Product::factory()->withPrices(unit_amount: 100.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 1); $cartItem = $cart->addToCart($price, quantity: 1);
@ -89,8 +89,8 @@ class CartManagementTest extends TestCase
public function it_calculates_cart_total_correctly() public function it_calculates_cart_total_correctly()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product1 = Product::factory()->withPrices(unit_amount: 5000)->create(); $product1 = Product::factory()->withPrices(unit_amount: 50.00)->create();
$product2 = Product::factory()->withPrices(unit_amount: 3000)->create(); $product2 = Product::factory()->withPrices(unit_amount: 30.00)->create();
$productPrice1 = $product1->defaultPrice()->first(); $productPrice1 = $product1->defaultPrice()->first();
$productPrice2 = $product2->defaultPrice()->first(); $productPrice2 = $product2->defaultPrice()->first();
@ -100,15 +100,15 @@ class CartManagementTest extends TestCase
$total = $cart->fresh()->getTotal(); $total = $cart->fresh()->getTotal();
$this->assertEquals(13000, $total); // (5000 * 2) + (3000 * 1) $this->assertEquals(130.00, $total); // (50 * 2) + (30 * 1)
} }
#[Test] #[Test]
public function it_calculates_total_items_correctly() public function it_calculates_total_items_correctly()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product1 = Product::factory()->withPrices(unit_amount: 1000)->create(); $product1 = Product::factory()->withPrices(unit_amount: 10.00)->create();
$product2 = Product::factory()->withPrices(unit_amount: 2000)->create(); $product2 = Product::factory()->withPrices(unit_amount: 20.00)->create();
$product1Price = $product1->defaultPrice()->first(); $product1Price = $product1->defaultPrice()->first();
$product2Price = $product2->defaultPrice()->first(); $product2Price = $product2->defaultPrice()->first();
@ -202,7 +202,7 @@ class CartManagementTest extends TestCase
public function cart_items_have_correct_relationships() public function cart_items_have_correct_relationships()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 4500)->create(); $product = Product::factory()->withPrices(unit_amount: 45.00)->create();
$productPrice = $product->defaultPrice()->first(); $productPrice = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($productPrice, quantity: 1); $cartItem = $cart->addToCart($productPrice, quantity: 1);
@ -215,19 +215,19 @@ class CartManagementTest extends TestCase
public function it_calculates_cart_item_subtotal() public function it_calculates_cart_item_subtotal()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create(); $product = Product::factory()->withPrices(unit_amount: 25.00)->create();
$productPrice = $product->defaultPrice()->first(); $productPrice = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($productPrice, quantity: 4); $cartItem = $cart->addToCart($productPrice, quantity: 4);
$this->assertEquals(10000, $cartItem->getSubtotal()); // 2500 * 4 $this->assertEquals(100.00, $cartItem->getSubtotal()); // 25 * 4
} }
#[Test] #[Test]
public function it_can_store_cart_item_attributes() public function it_can_store_cart_item_attributes()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$productPrice = $product->defaultPrice()->first(); $productPrice = $product->defaultPrice()->first();
$cartItem = $cart->addToCart( $cartItem = $cart->addToCart(
@ -247,7 +247,7 @@ class CartManagementTest extends TestCase
public function it_can_have_multiple_items_of_same_product_with_different_attributes() public function it_can_have_multiple_items_of_same_product_with_different_attributes()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 3000)->create(); $product = Product::factory()->withPrices(unit_amount: 30.00)->create();
$productPrice = $product->defaultPrice()->first(); $productPrice = $product->defaultPrice()->first();
$cart->addToCart( $cart->addToCart(
@ -270,7 +270,7 @@ class CartManagementTest extends TestCase
public function it_deletes_cart_items_when_cart_is_deleted() public function it_deletes_cart_items_when_cart_is_deleted()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 7500)->create(); $product = Product::factory()->withPrices(unit_amount: 75.00)->create();
$productPrice = $product->defaultPrice()->first(); $productPrice = $product->defaultPrice()->first();
$cartItem = $cart->addToCart( $cartItem = $cart->addToCart(
@ -317,14 +317,14 @@ class CartManagementTest extends TestCase
$cartWithProduct = Cart::factory() $cartWithProduct = Cart::factory()
->withNewProductInCart( ->withNewProductInCart(
quantity: 2, quantity: 2,
unit_amount: 15000, unit_amount: 150.00,
sale_unit_amount: 12000, sale_unit_amount: 120.00,
stocks: 10 stocks: 10
) )
->withNewProductInCart( ->withNewProductInCart(
quantity: 2, quantity: 2,
unit_amount: 15000, unit_amount: 150.00,
sale_unit_amount: 12000, sale_unit_amount: 120.00,
stocks: 10, stocks: 10,
sale_start: now()->subDay(), sale_start: now()->subDay(),
sale_end: now()->addDay() sale_end: now()->addDay()
@ -333,14 +333,14 @@ class CartManagementTest extends TestCase
$this->assertCount(2, $cartWithProduct->items); $this->assertCount(2, $cartWithProduct->items);
$this->assertEquals(4, $cartWithProduct->getTotalItems()); $this->assertEquals(4, $cartWithProduct->getTotalItems());
$this->assertEquals((15000 * 2) + (12000 * 2), $cartWithProduct->getTotal()); $this->assertEquals((150.00 * 2) + (120 * 2), $cartWithProduct->getTotal());
} }
#[Test] #[Test]
public function it_can_remove_entire_cart_item() public function it_can_remove_entire_cart_item()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 2); $cartItem = $cart->addToCart($price, quantity: 2);
@ -356,7 +356,7 @@ class CartManagementTest extends TestCase
public function it_can_decrease_cart_item_quantity() public function it_can_decrease_cart_item_quantity()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 7500)->create(); $product = Product::factory()->withPrices(unit_amount: 75.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 5); $cartItem = $cart->addToCart($price, quantity: 5);
@ -366,14 +366,14 @@ class CartManagementTest extends TestCase
$updatedItem = $cart->items->first(); $updatedItem = $cart->items->first();
$this->assertEquals(3, $updatedItem->quantity); $this->assertEquals(3, $updatedItem->quantity);
$this->assertEquals(7500 * 3, $updatedItem->subtotal); $this->assertEquals(75.00 * 3, $updatedItem->subtotal);
} }
#[Test] #[Test]
public function it_updates_subtotal_correctly_when_decreasing_quantity() public function it_updates_subtotal_correctly_when_decreasing_quantity()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create(); $product = Product::factory()->withPrices(unit_amount: 100.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cart->addToCart($price, quantity: 4); $cart->addToCart($price, quantity: 4);
@ -382,14 +382,14 @@ class CartManagementTest extends TestCase
$cartItem = $cart->items->first(); $cartItem = $cart->items->first();
$this->assertEquals(3, $cartItem->quantity); $this->assertEquals(3, $cartItem->quantity);
$this->assertEquals(30000, $cartItem->subtotal); $this->assertEquals(300.00, $cartItem->subtotal);
} }
#[Test] #[Test]
public function it_respects_parameters_when_removing_from_cart() public function it_respects_parameters_when_removing_from_cart()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
// Add same product with different parameters // Add same product with different parameters
@ -419,7 +419,7 @@ class CartManagementTest extends TestCase
public function it_decreases_only_matching_parameters_when_removing() public function it_decreases_only_matching_parameters_when_removing()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cart->addToCart( $cart->addToCart(
@ -439,7 +439,7 @@ class CartManagementTest extends TestCase
public function it_returns_cart_item_when_quantity_is_decreased() public function it_returns_cart_item_when_quantity_is_decreased()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cart->addToCart($price, quantity: 5); $cart->addToCart($price, quantity: 5);
@ -454,7 +454,7 @@ class CartManagementTest extends TestCase
public function it_handles_removing_nonexistent_item_gracefully() public function it_handles_removing_nonexistent_item_gracefully()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$result = $cart->removeFromCart($price, quantity: 1); $result = $cart->removeFromCart($price, quantity: 1);
@ -468,23 +468,23 @@ class CartManagementTest extends TestCase
public function it_updates_cart_total_after_removing_items() public function it_updates_cart_total_after_removing_items()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cart->addToCart($price, quantity: 5); $cart->addToCart($price, quantity: 5);
$this->assertEquals(25000, $cart->getTotal()); $this->assertEquals(250.00, $cart->getTotal());
$cart->removeFromCart($price, quantity: 2); $cart->removeFromCart($price, quantity: 2);
$this->assertEquals(15000, $cart->refresh()->getTotal()); $this->assertEquals(150.00, $cart->refresh()->getTotal());
} }
#[Test] #[Test]
public function it_can_remove_from_cart_with_multiple_items() public function it_can_remove_from_cart_with_multiple_items()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product1 = Product::factory()->withPrices(unit_amount: 5000)->create(); $product1 = Product::factory()->withPrices(unit_amount: 50.00)->create();
$product2 = Product::factory()->withPrices(unit_amount: 7500)->create(); $product2 = Product::factory()->withPrices(unit_amount: 75.00)->create();
$price1 = $product1->defaultPrice()->first(); $price1 = $product1->defaultPrice()->first();
$price2 = $product2->defaultPrice()->first(); $price2 = $product2->defaultPrice()->first();
@ -497,6 +497,6 @@ class CartManagementTest extends TestCase
$this->assertCount(1, $cart->refresh()->items); $this->assertCount(1, $cart->refresh()->items);
$this->assertEquals($price2->id, $cart->items->first()->purchasable_id); $this->assertEquals($price2->id, $cart->items->first()->purchasable_id);
$this->assertEquals(22500, $cart->getTotal()); $this->assertEquals(225.00, $cart->getTotal());
} }
} }

View File

@ -222,11 +222,9 @@ class CartServiceBookingTest extends TestCase
$cartItem = Cart::addBooking($this->bookingProduct, 2, $from, $until); $cartItem = Cart::addBooking($this->bookingProduct, 2, $from, $until);
// withPrices(1, 10000) stores 10000 cents // Price should be: price_per_day (10000 cents = 100 dollars) × days (3) = 30000 cents per unit
// Price should be: price_per_day (10000 cents) × days (3) = 30000 cents per unit
// Total should be: 30000 × quantity (2) = 60000 cents // Total should be: 30000 × quantity (2) = 60000 cents
$pricePerDay = 10000; // 100.00 euros = 10000 cents $expectedPricePerUnit = 10000 * $days; // 30000 cents
$expectedPricePerUnit = $pricePerDay * $days; // 30000 cents
$expectedTotal = $expectedPricePerUnit * 2; // 60000 cents $expectedTotal = $expectedPricePerUnit * 2; // 60000 cents
$this->assertEquals($expectedPricePerUnit, $cartItem->price); $this->assertEquals($expectedPricePerUnit, $cartItem->price);

View File

@ -80,7 +80,7 @@ class GuestCartTest extends TestCase
$total = Cart::total($guestCart); $total = Cart::total($guestCart);
$this->assertEquals(250, $total); $this->assertEquals(250.00, $total);
} }
#[Test] #[Test]
@ -167,8 +167,8 @@ class GuestCartTest extends TestCase
Cart::add($product, quantity: 1); Cart::add($product, quantity: 1);
// Verify they're different // Verify they're different
$this->assertEquals(100, Cart::total($guestCart)); $this->assertEquals(100.00, Cart::total($guestCart));
$this->assertEquals(100, Cart::total()); // Current user's cart $this->assertEquals(100.00, Cart::total()); // Current user's cart
$guestCartItems = Cart::items($guestCart); $guestCartItems = Cart::items($guestCart);
$userCartItems = Cart::items(); $userCartItems = Cart::items();
@ -195,8 +195,8 @@ class GuestCartTest extends TestCase
$userCart->addToCart($product, quantity: 2); $userCart->addToCart($product, quantity: 2);
// Original guest cart should still exist and be separate // Original guest cart should still exist and be separate
$this->assertEquals(200, Cart::total($guestCart)); $this->assertEquals(200.00, Cart::total($guestCart));
$this->assertEquals(200, Cart::total($userCart)); $this->assertEquals(200.00, Cart::total($userCart));
} }
#[Test] #[Test]
@ -226,8 +226,8 @@ class GuestCartTest extends TestCase
$guestCart2->addToCart($product, quantity: 3); // 300 $guestCart2->addToCart($product, quantity: 3); // 300
$this->assertNotEquals($guestCart1->id, $guestCart2->id); $this->assertNotEquals($guestCart1->id, $guestCart2->id);
$this->assertEquals(100, Cart::total($guestCart1)); $this->assertEquals(100.00, Cart::total($guestCart1));
$this->assertEquals(300, Cart::total($guestCart2)); $this->assertEquals(300.00, Cart::total($guestCart2));
} }
#[Test] #[Test]
@ -237,12 +237,12 @@ class GuestCartTest extends TestCase
$product = Product::factory()->withStocks(50)->withPrices(1, 50)->create(); $product = Product::factory()->withStocks(50)->withPrices(1, 50)->create();
$cartItem = $guestCart->addToCart($product, quantity: 2); $cartItem = $guestCart->addToCart($product, quantity: 2);
$this->assertEquals(100, $cartItem->subtotal); $this->assertEquals(100.00, $cartItem->subtotal);
$updated = Cart::update($cartItem, quantity: 5); $updated = Cart::update($cartItem, quantity: 5);
$this->assertEquals(5, $updated->quantity); $this->assertEquals(5, $updated->quantity);
$this->assertEquals(250, $updated->subtotal); $this->assertEquals(250.00, $updated->subtotal);
} }
#[Test] #[Test]

View File

@ -27,7 +27,7 @@ class OrderCheckoutFlowTest extends TestCase
public function checkout_creates_order_from_cart() public function checkout_creates_order_from_cart()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -44,7 +44,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_has_correct_cart_id() public function order_has_correct_cart_id()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -60,7 +60,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_has_correct_customer_info() public function order_has_correct_customer_info()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -81,7 +81,7 @@ class OrderCheckoutFlowTest extends TestCase
$cart = Cart::factory()->forCustomer($user)->create([ $cart = Cart::factory()->forCustomer($user)->create([
'currency' => 'EUR', 'currency' => 'EUR',
]); ]);
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -97,20 +97,20 @@ class OrderCheckoutFlowTest extends TestCase
public function order_has_correct_total_amount() public function order_has_correct_total_amount()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product1 = Product::factory()->withPrices(unit_amount: 5000)->create([ $product1 = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
$product2 = Product::factory()->withPrices(unit_amount: 3000)->create([ $product2 = Product::factory()->withPrices(unit_amount: 30.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
$user->addToCart($product1, quantity: 2); // 10000 cents $user->addToCart($product1, quantity: 2); // 100.00
$user->addToCart($product2, quantity: 3); // 9000 cents $user->addToCart($product2, quantity: 3); // 90.00
$cart = $user->checkoutCart(); $cart = $user->checkoutCart();
$order = $cart->order; $order = $cart->order;
// Total should be 19000 cents // Total should be 190.00 (19000 cents)
$this->assertEquals(19000, $order->amount_total); $this->assertEquals(19000, $order->amount_total);
$this->assertEquals(19000, $order->amount_subtotal); $this->assertEquals(19000, $order->amount_subtotal);
} }
@ -119,7 +119,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_starts_with_pending_status() public function order_starts_with_pending_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -135,7 +135,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_starts_with_zero_paid_amount() public function order_starts_with_zero_paid_amount()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -153,7 +153,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_has_unique_order_number() public function order_has_unique_order_number()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -173,7 +173,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_creation_adds_system_note() public function order_creation_adds_system_note()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -198,10 +198,10 @@ class OrderCheckoutFlowTest extends TestCase
public function order_has_purchases_through_cart() public function order_has_purchases_through_cart()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product1 = Product::factory()->withPrices(unit_amount: 5000)->create([ $product1 = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
$product2 = Product::factory()->withPrices(unit_amount: 3000)->create([ $product2 = Product::factory()->withPrices(unit_amount: 30.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -218,7 +218,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_purchases_have_correct_status() public function order_purchases_have_correct_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -238,7 +238,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_payment_updates_status() public function order_payment_updates_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -260,7 +260,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_payment_updates_purchase_status() public function order_payment_updates_purchase_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -280,7 +280,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_partial_payment_does_not_complete_order() public function order_partial_payment_does_not_complete_order()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -306,7 +306,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_can_be_processed_after_payment() public function order_can_be_processed_after_payment()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -324,7 +324,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_can_be_shipped_with_tracking() public function order_can_be_shipped_with_tracking()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -346,7 +346,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_can_be_completed() public function order_can_be_completed()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
'virtual' => true, // Virtual product 'virtual' => true, // Virtual product
]); ]);
@ -372,7 +372,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_can_be_cancelled() public function order_can_be_cancelled()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -403,7 +403,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_can_be_refunded() public function order_can_be_refunded()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -425,7 +425,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_partial_refund_does_not_change_status() public function order_partial_refund_does_not_change_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -450,7 +450,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_logs_status_changes() public function order_logs_status_changes()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -473,7 +473,7 @@ class OrderCheckoutFlowTest extends TestCase
public function order_logs_payment_notes() public function order_logs_payment_notes()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -499,7 +499,7 @@ class OrderCheckoutFlowTest extends TestCase
public function cart_status_is_converted_after_checkout() public function cart_status_is_converted_after_checkout()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -519,7 +519,7 @@ class OrderCheckoutFlowTest extends TestCase
$user = User::factory()->create(); $user = User::factory()->create();
$products = Product::factory() $products = Product::factory()
->withPrices(unit_amount: 2500) ->withPrices(unit_amount: 25.00)
->count(5) ->count(5)
->create(['manage_stock' => false]); ->create(['manage_stock' => false]);
@ -543,7 +543,7 @@ class OrderCheckoutFlowTest extends TestCase
{ {
$user1 = User::factory()->create(); $user1 = User::factory()->create();
$user2 = User::factory()->create(); $user2 = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -567,7 +567,7 @@ class OrderCheckoutFlowTest extends TestCase
public function can_find_paid_orders() public function can_find_paid_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);

View File

@ -21,7 +21,7 @@ class PurchaseFlowTest extends TestCase
public function user_can_purchase_a_product_directly() public function user_can_purchase_a_product_directly()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 4999)->create([ $product = Product::factory()->withPrices(unit_amount: 49.99)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -141,7 +141,7 @@ class PurchaseFlowTest extends TestCase
$total = $user->getCartTotal(); $total = $user->getCartTotal();
$this->assertEquals(140, $total); $this->assertEquals(140.00, $total);
} }
#[Test] #[Test]
@ -336,6 +336,6 @@ class PurchaseFlowTest extends TestCase
$cart = $user->checkoutCart(); $cart = $user->checkoutCart();
$this->assertEquals(170, $cart->getTotal()); $this->assertEquals(170.00, $cart->getTotal());
} }
} }

View File

@ -210,7 +210,7 @@ class HasShoppingCapabilitiesTest extends TestCase
$total = $user->getCartTotal(); $total = $user->getCartTotal();
$this->assertEquals(250, $total); $this->assertEquals(250.00, $total);
} }
#[Test] #[Test]

View File

@ -140,10 +140,9 @@ class PoolProductPriceIdTest extends TestCase
$this->assertNotNull($cartItem->product_id); $this->assertNotNull($cartItem->product_id);
$this->assertEquals($this->singleItem1->id, $cartItem->product_id); $this->assertEquals($this->singleItem1->id, $cartItem->product_id);
// Verify we can get the actual product through the relationship // Meta should still have the name for display purposes
$allocatedProduct = $cartItem->product; $meta = $cartItem->getMeta();
$this->assertNotNull($allocatedProduct); $this->assertEquals($this->singleItem1->name, $meta->allocated_single_item_name);
$this->assertEquals($this->singleItem1->name, $allocatedProduct->name);
} }
#[Test] #[Test]

View File

@ -267,9 +267,9 @@ class PoolProductionBugTest extends TestCase
// The 3x 5000 items might be merged since they have the same price_id (pool price) // The 3x 5000 items might be merged since they have the same price_id (pool price)
// But different single items should NOT be merged // But different single items should NOT be merged
// Get all allocated single item names via product relationship // Get all allocated single item names
$allocatedNames = $items->map(fn($item) => [ $allocatedNames = $items->map(fn($item) => [
'name' => $item->product?->name ?? 'unknown', 'name' => $item->getMeta()->allocated_single_item_name ?? 'unknown',
'price' => $item->price, 'price' => $item->price,
'quantity' => $item->quantity, 'quantity' => $item->quantity,
])->toArray(); ])->toArray();
@ -787,7 +787,7 @@ class PoolProductionBugTest extends TestCase
'manage_stock' => true, 'manage_stock' => true,
]); ]);
// single_1: 3 stock @ 1000/day (10.00 euros) // single_1: 3 stock @ 1000/day
$single_1 = Product::factory() $single_1 = Product::factory()
->withStocks(3) ->withStocks(3)
->withPrices(1, 1000) ->withPrices(1, 1000)
@ -797,7 +797,7 @@ class PoolProductionBugTest extends TestCase
'name' => 'Single1-Cheap', 'name' => 'Single1-Cheap',
]); ]);
// single_2: 1 stock @ 10001/day (100.01 euros) // single_2: 1 stock @ 10001/day
$single_2 = Product::factory() $single_2 = Product::factory()
->withStocks(1) ->withStocks(1)
->withPrices(1, 10001) ->withPrices(1, 10001)
@ -807,7 +807,7 @@ class PoolProductionBugTest extends TestCase
'name' => 'Single2-Medium', 'name' => 'Single2-Medium',
]); ]);
// single_3: 1 stock @ 10002/day (100.02 euros) // single_3: 1 stock @ 10002/day
$single_3 = Product::factory() $single_3 = Product::factory()
->withStocks(1) ->withStocks(1)
->withPrices(1, 10002) ->withPrices(1, 10002)
@ -833,12 +833,13 @@ class PoolProductionBugTest extends TestCase
$items = []; $items = [];
for ($i = 1; $i <= 4; $i++) { for ($i = 1; $i <= 4; $i++) {
$item = $cart->addToCart($pool, 1); $item = $cart->addToCart($pool, 1);
$meta = $item->getMeta();
$items[$i] = [ $items[$i] = [
'id' => $item->id, 'id' => $item->id,
'quantity' => $item->quantity, 'quantity' => $item->quantity,
'price' => $item->price, 'price' => $item->price,
'allocated_id' => $item->product_id, 'allocated_id' => $item->product_id,
'allocated_name' => $item->product?->name ?? 'none', 'allocated_name' => $meta->allocated_single_item_name ?? 'none',
]; ];
} }
@ -849,12 +850,13 @@ class PoolProductionBugTest extends TestCase
$cartItemDetails = []; $cartItemDetails = [];
$totalQuantity = 0; $totalQuantity = 0;
foreach ($cartItems as $item) { foreach ($cartItems as $item) {
$meta = $item->getMeta();
$cartItemDetails[] = [ $cartItemDetails[] = [
'id' => $item->id, 'id' => $item->id,
'quantity' => $item->quantity, 'quantity' => $item->quantity,
'price' => $item->price, 'price' => $item->price,
'allocated_id' => $item->product_id, 'allocated_id' => $item->product_id,
'allocated_name' => $item->product?->name ?? 'none', 'allocated_name' => $meta->allocated_single_item_name ?? 'none',
]; ];
$totalQuantity += $item->quantity; $totalQuantity += $item->quantity;
} }
@ -1042,7 +1044,7 @@ class PoolProductionBugTest extends TestCase
'manage_stock' => true, 'manage_stock' => true,
]); ]);
// single_1: 2 stock @ 1000/day (10.00 euros) // single_1: 2 stock @ 1000/day
$single_1 = Product::factory() $single_1 = Product::factory()
->withStocks(2) ->withStocks(2)
->withPrices(1, 1000) ->withPrices(1, 1000)
@ -1052,7 +1054,7 @@ class PoolProductionBugTest extends TestCase
'name' => 'Single1', 'name' => 'Single1',
]); ]);
// single_2: 1 stock @ 2000/day (20.00 euros) // single_2: 1 stock @ 2000/day
$single_2 = Product::factory() $single_2 = Product::factory()
->withStocks(1) ->withStocks(1)
->withPrices(1, 2000) ->withPrices(1, 2000)

View File

@ -343,8 +343,7 @@ class PoolSmartAllocationTest extends TestCase
$cart->addToCart($this->pool, 3, [], $claimFrom, $claimUntil); $cart->addToCart($this->pool, 3, [], $claimFrom, $claimUntil);
$initialItems = $cart->fresh()->items->sortBy('price')->values(); $initialItems = $cart->fresh()->items->sortBy('price')->values();
// Use product relationship to get allocated single item names $initialAllocations = $initialItems->map(fn($i) => $i->getMeta()->allocated_single_item_name)->toArray();
$initialAllocations = $initialItems->map(fn($i) => $i->product?->name)->toArray();
// Should have expensive items allocated // Should have expensive items allocated
$this->assertContains('Spot 4 - 40000', $initialAllocations); $this->assertContains('Spot 4 - 40000', $initialAllocations);
@ -356,8 +355,7 @@ class PoolSmartAllocationTest extends TestCase
$cart->setDates($newFrom, $newUntil); $cart->setDates($newFrom, $newUntil);
$newItems = $cart->fresh()->items->sortBy('price')->values(); $newItems = $cart->fresh()->items->sortBy('price')->values();
// Use product relationship to get allocated single item names $newAllocations = $newItems->map(fn($i) => $i->getMeta()->allocated_single_item_name)->toArray();
$newAllocations = $newItems->map(fn($i) => $i->product?->name)->toArray();
// Should now have cheap items allocated // Should now have cheap items allocated
$this->assertContains('Spot 1 - 10000', $newAllocations); $this->assertContains('Spot 1 - 10000', $newAllocations);

View File

@ -21,7 +21,7 @@ class ProductManagementTest extends TestCase
public function it_can_create_a_product() public function it_can_create_a_product()
{ {
$product = Product::factory() $product = Product::factory()
->withPrices(1, 9999) ->withPrices(1, 99.99)
->create([ ->create([
'slug' => 'test-product', 'slug' => 'test-product',
'type' => 'simple', 'type' => 'simple',
@ -33,8 +33,7 @@ class ProductManagementTest extends TestCase
]); ]);
$this->assertCount(1, $product->prices); $this->assertCount(1, $product->prices);
// Factory converts 99.99 euros to 9999 cents $this->assertEquals(99.99, $product->prices->first()->unit_amount);
$this->assertEquals(9999, $product->prices->first()->unit_amount);
} }
#[Test] #[Test]

View File

@ -61,12 +61,11 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_filter_by_price_range() public function it_can_filter_by_price_range()
{ {
$product1 = Product::factory()->withPrices(1, 5000)->create(); $product1 = Product::factory()->withPrices(1, 50)->create();
$product2 = Product::factory()->withPrices(1, 10000)->create(); $product2 = Product::factory()->withPrices(1, 100)->create();
$product3 = Product::factory()->withPrices(1, 15000)->create(); $product3 = Product::factory()->withPrices(1, 150)->create();
// Filter in cents: 7500-12500 cents = $75-$125 $inRange = Product::priceRange(75, 125)->get();
$inRange = Product::priceRange(7500, 12500)->get();
$this->assertCount(1, $inRange); $this->assertCount(1, $inRange);
$this->assertTrue($inRange->contains($product2)); $this->assertTrue($inRange->contains($product2));
@ -75,12 +74,11 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_filter_by_minimum_price_only() public function it_can_filter_by_minimum_price_only()
{ {
$product1 = Product::factory()->withPrices(1, 5000)->create(); $product1 = Product::factory()->withPrices(1, 50)->create();
$product2 = Product::factory()->withPrices(1, 10000)->create(); $product2 = Product::factory()->withPrices(1, 100)->create();
$product3 = Product::factory()->withPrices(1, 15000)->create(); $product3 = Product::factory()->withPrices(1, 150)->create();
// Filter minimum in cents: 10000 cents = $100 $minPrice = Product::priceRange(100)->get();
$minPrice = Product::priceRange(10000)->get();
$this->assertCount(2, $minPrice); $this->assertCount(2, $minPrice);
$this->assertTrue($minPrice->contains($product2)); $this->assertTrue($minPrice->contains($product2));
@ -90,12 +88,11 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_filter_by_maximum_price_only() public function it_can_filter_by_maximum_price_only()
{ {
$product1 = Product::factory()->withPrices(1, 5000)->create(); $product1 = Product::factory()->withPrices(1, 50)->create();
$product2 = Product::factory()->withPrices(1, 10000)->create(); $product2 = Product::factory()->withPrices(1, 100)->create();
$product3 = Product::factory()->withPrices(1, 15000)->create(); $product3 = Product::factory()->withPrices(1, 150)->create();
// Filter maximum in cents: 10000 cents = $100 $maxPrice = Product::priceRange(null, 100)->get();
$maxPrice = Product::priceRange(null, 10000)->get();
$this->assertCount(2, $maxPrice); $this->assertCount(2, $maxPrice);
$this->assertTrue($maxPrice->contains($product1)); $this->assertTrue($maxPrice->contains($product1));
@ -105,9 +102,9 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_order_products_by_price_ascending() public function it_can_order_products_by_price_ascending()
{ {
$product1 = Product::factory()->withPrices(1, 15000)->create(['name' => 'Expensive']); $product1 = Product::factory()->withPrices(1, 150)->create(['name' => 'Expensive']);
$product2 = Product::factory()->withPrices(1, 5000)->create(['name' => 'Cheap']); $product2 = Product::factory()->withPrices(1, 50)->create(['name' => 'Cheap']);
$product3 = Product::factory()->withPrices(1, 10000)->create(['name' => 'Medium']); $product3 = Product::factory()->withPrices(1, 100)->create(['name' => 'Medium']);
$ordered = Product::orderByPrice('asc')->get(); $ordered = Product::orderByPrice('asc')->get();
@ -118,9 +115,9 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_order_products_by_price_descending() public function it_can_order_products_by_price_descending()
{ {
$product1 = Product::factory()->withPrices(1, 15000)->create(['name' => 'Expensive']); $product1 = Product::factory()->withPrices(1, 150)->create(['name' => 'Expensive']);
$product2 = Product::factory()->withPrices(1, 5000)->create(['name' => 'Cheap']); $product2 = Product::factory()->withPrices(1, 50)->create(['name' => 'Cheap']);
$product3 = Product::factory()->withPrices(1, 10000)->create(['name' => 'Medium']); $product3 = Product::factory()->withPrices(1, 100)->create(['name' => 'Medium']);
$ordered = Product::orderByPrice('desc')->get(); $ordered = Product::orderByPrice('desc')->get();
@ -131,13 +128,12 @@ class ProductScopeTest extends TestCase
#[Test] #[Test]
public function it_can_combine_price_range_and_order_by_price() public function it_can_combine_price_range_and_order_by_price()
{ {
$product1 = Product::factory()->withPrices(1, 5000)->create(); $product1 = Product::factory()->withPrices(1, 50)->create();
$product2 = Product::factory()->withPrices(1, 10000)->create(); $product2 = Product::factory()->withPrices(1, 100)->create();
$product3 = Product::factory()->withPrices(1, 15000)->create(); $product3 = Product::factory()->withPrices(1, 150)->create();
$product4 = Product::factory()->withPrices(1, 20000)->create(); $product4 = Product::factory()->withPrices(1, 200)->create();
// Filter in cents: 7500-17500 cents = $75-$175 $filtered = Product::priceRange(75, 175)->orderByPrice('asc')->get();
$filtered = Product::priceRange(7500, 17500)->orderByPrice('asc')->get();
$this->assertCount(2, $filtered); $this->assertCount(2, $filtered);
$this->assertEquals($product2->id, $filtered->first()->id); $this->assertEquals($product2->id, $filtered->first()->id);

View File

@ -20,7 +20,7 @@ class CartItemTest extends TestCase
public function cart_item_stores_prices_as_integers_in_cents() public function cart_item_stores_prices_as_integers_in_cents()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 1550)->create(); // 1550 cents $product = Product::factory()->withPrices(unit_amount: 1550)->create(); // $15.50 in cents
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 2); $cartItem = $cart->addToCart($price, quantity: 2);
@ -42,7 +42,7 @@ class CartItemTest extends TestCase
public function cart_item_unit_amount_represents_base_price_per_day() public function cart_item_unit_amount_represents_base_price_per_day()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); // 5000 cents per day $product = Product::factory()->withPrices(unit_amount: 5000)->create(); // $50.00 per day
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 1); $cartItem = $cart->addToCart($price, quantity: 1);
@ -56,7 +56,7 @@ class CartItemTest extends TestCase
public function cart_item_calculates_price_correctly_for_booking_timespan() public function cart_item_calculates_price_correctly_for_booking_timespan()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 2000)->create(['type' => ProductType::BOOKING]); // 2000 cents per day $product = Product::factory()->withPrices(unit_amount: 2000)->create(['type' => ProductType::BOOKING]); // $20.00 per day
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$from = Carbon::parse('2025-01-01 00:00:00'); $from = Carbon::parse('2025-01-01 00:00:00');
@ -78,7 +78,7 @@ class CartItemTest extends TestCase
public function cart_item_calculates_price_with_partial_days() public function cart_item_calculates_price_with_partial_days()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 4800)->create(['type' => ProductType::BOOKING]); // 4800 cents per day $product = Product::factory()->withPrices(unit_amount: 4800)->create(['type' => ProductType::BOOKING]); // $48.00 per day
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
// 12 hours = 0.5 days // 12 hours = 0.5 days
@ -112,7 +112,7 @@ class CartItemTest extends TestCase
public function cart_item_updates_prices_when_dates_change() public function cart_item_updates_prices_when_dates_change()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 3000)->withStocks(10)->create(['type' => ProductType::BOOKING]); // 3000 cents per day $product = Product::factory()->withPrices(unit_amount: 3000)->withStocks(10)->create(['type' => ProductType::BOOKING]); // $30.00 per day
$from = Carbon::parse('2025-01-01 00:00:00'); $from = Carbon::parse('2025-01-01 00:00:00');
$until = Carbon::parse('2025-01-02 00:00:00'); // 1 day $until = Carbon::parse('2025-01-02 00:00:00'); // 1 day
@ -145,7 +145,7 @@ class CartItemTest extends TestCase
public function cart_item_handles_fractional_days_with_multiple_quantities() public function cart_item_handles_fractional_days_with_multiple_quantities()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 2400)->create(['type' => ProductType::BOOKING]); // 2400 cents per day $product = Product::factory()->withPrices(unit_amount: 2400)->create(['type' => ProductType::BOOKING]); // $24.00 per day
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
// 1.5 days // 1.5 days
@ -236,7 +236,7 @@ class CartItemTest extends TestCase
$cart = Cart::create(); $cart = Cart::create();
// Create a product with a price that will result in fractional cents when multiplied // Create a product with a price that will result in fractional cents when multiplied
$product = Product::factory()->withPrices(unit_amount: 3333)->create(['type' => ProductType::BOOKING]); // 3333 cents $product = Product::factory()->withPrices(unit_amount: 3333)->create(['type' => ProductType::BOOKING]); // $33.33
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
// 1.5 days should give 3333 * 1.5 = 4999.5 cents // 1.5 days should give 3333 * 1.5 = 4999.5 cents
@ -329,7 +329,7 @@ class CartItemTest extends TestCase
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory() $product = Product::factory()
->withPrices(unit_amount: 500) ->withPrices(unit_amount: 500)
->create(['type' => ProductType::BOOKING]); // 500 cents per day ->create(['type' => ProductType::BOOKING]); // $5.00 per day
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();

View File

@ -19,26 +19,26 @@ class CartTest extends TestCase
public function cart_can_add_product_price_directly() public function cart_can_add_product_price_directly()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create(); $product = Product::factory()->withPrices(unit_amount: 100.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 2); $cartItem = $cart->addToCart($price, quantity: 2);
$this->assertNotNull($cartItem); $this->assertNotNull($cartItem);
$this->assertEquals(2, $cartItem->quantity); $this->assertEquals(2, $cartItem->quantity);
$this->assertEquals(10000, $cartItem->price); $this->assertEquals(100.00, $cartItem->price);
} }
#[Test] #[Test]
public function cart_calculates_subtotal_automatically() public function cart_calculates_subtotal_automatically()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price, quantity: 3); $cartItem = $cart->addToCart($price, quantity: 3);
$this->assertEquals(15000, $cartItem->subtotal); $this->assertEquals(150.00, $cartItem->subtotal);
} }
#[Test] #[Test]
@ -78,7 +78,7 @@ class CartTest extends TestCase
public function cart_can_add_items_with_custom_parameters() public function cart_can_add_items_with_custom_parameters()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$parameters = [ $parameters = [
@ -99,10 +99,10 @@ class CartTest extends TestCase
{ {
$cart = Cart::create(); $cart = Cart::create();
$product1 = Product::factory()->withPrices(unit_amount: 2500)->create(); $product1 = Product::factory()->withPrices(unit_amount: 25.00)->create();
$price1 = $product1->defaultPrice()->first(); $price1 = $product1->defaultPrice()->first();
$product2 = Product::factory()->withPrices(unit_amount: 5000)->create(); $product2 = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price2 = $product2->defaultPrice()->first(); $price2 = $product2->defaultPrice()->first();
$cart->addToCart($price1, quantity: 2); // 50 $cart->addToCart($price1, quantity: 2); // 50
@ -110,7 +110,7 @@ class CartTest extends TestCase
$total = $cart->fresh()->getTotal(); $total = $cart->fresh()->getTotal();
$this->assertEquals(20000, $total); $this->assertEquals(200.00, $total);
} }
#[Test] #[Test]
@ -172,7 +172,7 @@ class CartTest extends TestCase
public function cart_deletes_items_on_deletion() public function cart_deletes_items_on_deletion()
{ {
$cart = Cart::create(); $cart = Cart::create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create(); $product = Product::factory()->withPrices(unit_amount: 50.00)->create();
$price = $product->defaultPrice()->first(); $price = $product->defaultPrice()->first();
$cartItem = $cart->addToCart($price); $cartItem = $cart->addToCart($price);

View File

@ -38,7 +38,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_have_multiple_orders() public function user_can_have_multiple_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -55,7 +55,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_pending_orders() public function user_can_get_pending_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -70,7 +70,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_processing_orders() public function user_can_get_processing_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -86,7 +86,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_completed_orders() public function user_can_get_completed_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -102,7 +102,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_active_orders() public function user_can_get_active_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -122,7 +122,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_paid_orders() public function user_can_get_paid_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -142,7 +142,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_fully_paid_orders() public function user_can_get_fully_paid_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -163,7 +163,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_latest_order() public function user_can_get_latest_order()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -183,7 +183,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_total_spent() public function user_can_get_total_spent()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -202,7 +202,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_order_count() public function user_can_get_order_count()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -219,7 +219,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_completed_order_count() public function user_can_get_completed_order_count()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -240,7 +240,7 @@ class HasOrdersTraitTest extends TestCase
$this->assertFalse($user->hasOrders()); $this->assertFalse($user->hasOrders());
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
$user->addToCart($product); $user->addToCart($product);
@ -253,7 +253,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_check_has_active_orders() public function user_can_check_has_active_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -272,7 +272,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_find_order_by_number() public function user_can_find_order_by_number()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -291,7 +291,7 @@ class HasOrdersTraitTest extends TestCase
{ {
$user1 = User::factory()->create(); $user1 = User::factory()->create();
$user2 = User::factory()->create(); $user2 = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -309,7 +309,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_orders_between_dates() public function user_can_get_orders_between_dates()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -328,7 +328,7 @@ class HasOrdersTraitTest extends TestCase
public function user_can_get_orders_with_specific_status() public function user_can_get_orders_with_specific_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);

View File

@ -153,7 +153,8 @@ class OrderNoteTest extends TestCase
'Note from user', 'Note from user',
'note', 'note',
false, false,
$user get_class($user),
$user->id
); );
$this->assertEquals(get_class($user), $note->author_type); $this->assertEquals(get_class($user), $note->author_type);

View File

@ -18,7 +18,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_total_revenue() public function order_can_get_total_revenue()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -37,7 +37,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_revenue_between_dates() public function order_can_get_revenue_between_dates()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -57,7 +57,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_total_refunded() public function order_can_get_total_refunded()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -73,7 +73,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_net_revenue() public function order_can_get_net_revenue()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -89,7 +89,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_average_order_value() public function order_can_get_average_order_value()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -107,7 +107,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_counts_by_status() public function order_can_get_counts_by_status()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -136,7 +136,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_revenue_summary() public function order_can_get_revenue_summary()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -162,7 +162,7 @@ class OrderSummaryTest extends TestCase
public function order_can_get_daily_revenue() public function order_can_get_daily_revenue()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -181,7 +181,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_today_returns_todays_orders() public function order_scope_today_returns_todays_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -195,7 +195,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_this_week_returns_this_weeks_orders() public function order_scope_this_week_returns_this_weeks_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -209,7 +209,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_this_month_returns_this_months_orders() public function order_scope_this_month_returns_this_months_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -223,7 +223,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_by_payment_provider_returns_filtered_orders() public function order_scope_by_payment_provider_returns_filtered_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -246,7 +246,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_with_refunds_returns_orders_with_refunds() public function order_scope_with_refunds_returns_orders_with_refunds()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -270,7 +270,7 @@ class OrderSummaryTest extends TestCase
public function order_scope_fully_refunded_returns_fully_refunded_orders() public function order_scope_fully_refunded_returns_fully_refunded_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);

View File

@ -599,299 +599,4 @@ class OrderTest extends TestCase
$this->assertEquals('USD 100.00', Order::formatMoney(10000, 'usd')); $this->assertEquals('USD 100.00', Order::formatMoney(10000, 'usd'));
$this->assertEquals('EUR 50.50', Order::formatMoney(5050, 'eur')); $this->assertEquals('EUR 50.50', Order::formatMoney(5050, 'eur'));
} }
// =========================================================================
// ORDER BOOKING DATE RANGE TESTS
// =========================================================================
#[Test]
public function order_can_have_from_and_until_dates()
{
$from = now()->addDay();
$until = now()->addDays(5);
$order = Order::factory()->withDateRange($from, $until)->create();
$this->assertEquals($from->format('Y-m-d H:i:s'), $order->from->format('Y-m-d H:i:s'));
$this->assertEquals($until->format('Y-m-d H:i:s'), $order->until->format('Y-m-d H:i:s'));
}
#[Test]
public function order_factory_booking_state_sets_default_dates()
{
$order = Order::factory()->booking()->create();
$this->assertNotNull($order->from);
$this->assertNotNull($order->until);
$this->assertTrue($order->from->lt($order->until));
}
#[Test]
public function order_created_from_cart_inherits_booking_dates()
{
$user = User::factory()->create();
$from = now()->addDay();
$until = now()->addDays(3);
$cart = Cart::factory()->forCustomer($user)->create([
'converted_at' => now(),
'from' => $from,
'until' => $until,
]);
$order = Order::createFromCart($cart);
$this->assertEquals($from->format('Y-m-d H:i:s'), $order->from->format('Y-m-d H:i:s'));
$this->assertEquals($until->format('Y-m-d H:i:s'), $order->until->format('Y-m-d H:i:s'));
}
#[Test]
public function order_without_booking_dates_has_null_from_until()
{
$user = User::factory()->create();
$cart = Cart::factory()->forCustomer($user)->create([
'converted_at' => now(),
]);
$order = Order::createFromCart($cart);
$this->assertNull($order->from);
$this->assertNull($order->until);
}
// =========================================================================
// ORDER AUTOMATIC LOG CREATION TESTS
// =========================================================================
#[Test]
public function order_status_change_automatically_creates_log()
{
$order = Order::factory()->pending()->create();
$order->updateStatus(OrderStatus::PROCESSING);
$statusNote = $order->notes()
->where('type', OrderNote::TYPE_STATUS_CHANGE)
->first();
$this->assertNotNull($statusNote);
$this->assertStringContainsString('Pending Payment', $statusNote->content);
$this->assertStringContainsString('Processing', $statusNote->content);
}
#[Test]
public function order_payment_automatically_creates_log()
{
$order = Order::factory()->pending()->create([
'amount_total' => 10000,
'amount_paid' => 0,
]);
$order->recordPayment(5000, 'pi_test123', 'card', 'stripe');
$paymentNote = $order->notes()
->where('type', OrderNote::TYPE_PAYMENT)
->first();
$this->assertNotNull($paymentNote);
$this->assertStringContainsString('50.00', $paymentNote->content);
}
#[Test]
public function order_refund_automatically_creates_log()
{
$order = Order::factory()->paid()->create([
'amount_total' => 10000,
'amount_paid' => 10000,
]);
$order->recordRefund(3000, 'Partial refund');
$refundNote = $order->notes()
->where('type', OrderNote::TYPE_REFUND)
->first();
$this->assertNotNull($refundNote);
$this->assertStringContainsString('30.00', $refundNote->content);
}
#[Test]
public function order_shipping_creates_log_with_tracking()
{
$order = Order::factory()->processing()->create();
$order->markAsShipped('TRACK123456', 'FedEx');
$shippingNote = $order->notes()
->where('type', OrderNote::TYPE_STATUS_CHANGE)
->orderBy('created_at', 'desc')
->first();
$this->assertNotNull($shippingNote);
$this->assertStringContainsString('TRACK123456', $shippingNote->content);
$this->assertStringContainsString('FedEx', $shippingNote->content);
}
#[Test]
public function order_cancellation_creates_log_with_reason()
{
$order = Order::factory()->pending()->create();
$order->cancel('Customer changed their mind');
$cancelNote = $order->notes()
->where('type', OrderNote::TYPE_STATUS_CHANGE)
->orderBy('created_at', 'desc')
->first();
$this->assertNotNull($cancelNote);
$this->assertStringContainsString('Customer changed their mind', $cancelNote->content);
}
// =========================================================================
// ORDER MANUAL LOG CREATION TESTS
// =========================================================================
#[Test]
public function order_can_add_manual_internal_note()
{
$order = Order::factory()->create();
$note = $order->addNote('This is a manual internal note', OrderNote::TYPE_NOTE, false);
$this->assertEquals('This is a manual internal note', $note->content);
$this->assertEquals(OrderNote::TYPE_NOTE, $note->type);
$this->assertFalse($note->is_customer_note);
}
#[Test]
public function order_can_add_manual_customer_visible_note()
{
$order = Order::factory()->create();
$note = $order->addNote('Thank you for your order!', OrderNote::TYPE_CUSTOMER, true);
$this->assertEquals('Thank you for your order!', $note->content);
$this->assertTrue($note->is_customer_note);
$this->assertCount(1, $order->customerNotes);
}
#[Test]
public function order_can_add_note_with_author()
{
$order = Order::factory()->create();
$admin = User::factory()->create();
$note = $order->addNote(
'Admin reviewed this order',
OrderNote::TYPE_NOTE,
false,
$admin
);
$this->assertEquals($admin->id, $note->author_id);
$this->assertEquals(get_class($admin), $note->author_type);
$this->assertTrue($note->author->is($admin));
}
#[Test]
public function order_can_add_note_with_meta()
{
$order = Order::factory()->create();
$note = $order->addNote('Note with metadata', OrderNote::TYPE_SYSTEM, false, null, [
'source' => 'api',
'request_id' => 'req_12345',
]);
$this->assertEquals('api', $note->meta->source);
$this->assertEquals('req_12345', $note->meta->request_id);
}
#[Test]
public function order_notes_are_ordered_by_newest_first()
{
$order = Order::factory()->create();
$note1 = $order->addNote('First note');
sleep(1); // Ensure different timestamps
$note2 = $order->addNote('Second note');
$notes = $order->notes()->get();
$this->assertEquals($note2->id, $notes->first()->id);
$this->assertEquals($note1->id, $notes->last()->id);
}
#[Test]
public function order_logs_multiple_status_changes()
{
$order = Order::factory()->pending()->create();
$order->updateStatus(OrderStatus::PROCESSING);
$order->updateStatus(OrderStatus::IN_PREPARATION);
$order->updateStatus(OrderStatus::SHIPPED);
$statusNotes = $order->notes()
->where('type', OrderNote::TYPE_STATUS_CHANGE)
->get();
// Should have 3 status change notes
$this->assertCount(3, $statusNotes);
}
#[Test]
public function order_logs_multiple_partial_payments()
{
$order = Order::factory()->pending()->create([
'amount_total' => 10000,
'amount_paid' => 0,
]);
$order->recordPayment(3000);
$order->recordPayment(3000);
$order->recordPayment(4000);
$paymentNotes = $order->notes()
->where('type', OrderNote::TYPE_PAYMENT)
->get();
$this->assertCount(3, $paymentNotes);
}
// =========================================================================
// ORDER LOG FILTERING TESTS
// =========================================================================
#[Test]
public function order_can_filter_internal_notes()
{
$order = Order::factory()->create();
$order->addNote('Internal 1', OrderNote::TYPE_NOTE, false);
$order->addNote('Internal 2', OrderNote::TYPE_NOTE, false);
$order->addNote('Customer visible', OrderNote::TYPE_CUSTOMER, true);
$this->assertCount(2, $order->internalNotes);
}
#[Test]
public function order_can_get_notes_by_type()
{
$order = Order::factory()->pending()->create([
'amount_total' => 10000,
]);
$order->addNote('Manual note', OrderNote::TYPE_NOTE);
$order->recordPayment(5000);
$order->updateStatus(OrderStatus::PROCESSING);
$notesByType = $order->notes()->where('type', OrderNote::TYPE_NOTE)->get();
$paymentsByType = $order->notes()->where('type', OrderNote::TYPE_PAYMENT)->get();
$statusChangesByType = $order->notes()->where('type', OrderNote::TYPE_STATUS_CHANGE)->get();
$this->assertCount(1, $notesByType);
$this->assertCount(1, $paymentsByType);
$this->assertCount(1, $statusChangesByType);
}
} }

View File

@ -60,7 +60,7 @@ class ProductDuplicateTest extends TestCase
#[Test] #[Test]
public function duplicate_includes_prices() public function duplicate_includes_prices()
{ {
$product = Product::factory()->withPrices(unit_amount: 2500)->create(); $product = Product::factory()->withPrices(unit_amount: 25.00)->create();
$duplicate = $product->duplicate(); $duplicate = $product->duplicate();
@ -72,7 +72,7 @@ class ProductDuplicateTest extends TestCase
#[Test] #[Test]
public function duplicate_can_exclude_prices() public function duplicate_can_exclude_prices()
{ {
$product = Product::factory()->withPrices(unit_amount: 2500)->create(); $product = Product::factory()->withPrices(unit_amount: 25.00)->create();
$duplicate = $product->duplicate(includePrices: false); $duplicate = $product->duplicate(includePrices: false);

View File

@ -15,71 +15,71 @@ class ProductPricingTest extends TestCase
#[Test] #[Test]
public function it_returns_regular_price_when_not_on_sale() public function it_returns_regular_price_when_not_on_sale()
{ {
$product = Product::factory()->withPrices(2, 10000)->create(); $product = Product::factory()->withPrices(2, 100)->create();
$this->assertEquals(2, $product->prices()->count()); $this->assertEquals(2, $product->prices()->count());
$this->assertFalse($product->isOnSale()); $this->assertFalse($product->isOnSale());
$this->assertNotNull($product->defaultPrice()->first()); $this->assertNotNull($product->defaultPrice()->first());
$this->assertEquals(10000, $product->getCurrentPrice()); $this->assertEquals(100, $product->getCurrentPrice());
} }
#[Test] #[Test]
public function it_returns_sale_price_when_on_sale() public function it_returns_sale_price_when_on_sale()
{ {
$product = Product::factory() $product = Product::factory()
->withPrices(1, 10000) ->withPrices(1, 100)
->create([ ->create([
'sale_start' => now()->subDay(), 'sale_start' => now()->subDay(),
'sale_end' => now()->addDay(), 'sale_end' => now()->addDay(),
]); ]);
$price = $product->prices()->first(); $price = $product->prices()->first();
$price->sale_unit_amount = 8000; $price->sale_unit_amount = 80;
$price->save(); $price->save();
$this->assertEquals(8000, $product->getCurrentPrice()); $this->assertEquals(80, $product->getCurrentPrice());
} }
#[Test] #[Test]
public function it_returns_regular_price_when_sale_has_ended() public function it_returns_regular_price_when_sale_has_ended()
{ {
$product = Product::factory()->withPrices(1, 10000)->create([ $product = Product::factory()->withPrices(1, 100)->create([
'sale_start' => now()->subWeek(), 'sale_start' => now()->subWeek(),
'sale_end' => now()->addHour(), 'sale_end' => now()->addHour(),
]); ]);
$price = $product->prices()->first(); $price = $product->prices()->first();
$price->sale_unit_amount = 8000; $price->sale_unit_amount = 80;
$price->save(); $price->save();
$this->assertEquals(8000, $product->getCurrentPrice()); $this->assertEquals(80, $product->getCurrentPrice());
$product->update([ $product->update([
'sale_end' => now()->subHour(), 'sale_end' => now()->subHour(),
]); ]);
$this->assertEquals(10000, $product->getCurrentPrice()); $this->assertEquals(100, $product->getCurrentPrice());
} }
#[Test] #[Test]
public function it_returns_regular_price_when_sale_hasnt_started() public function it_returns_regular_price_when_sale_hasnt_started()
{ {
$product = Product::factory()->withPrices(1, 10000)->create([ $product = Product::factory()->withPrices(1, 100)->create([
'sale_start' => now()->addDay(), 'sale_start' => now()->addDay(),
'sale_end' => now()->addWeek(), 'sale_end' => now()->addWeek(),
]); ]);
$price = $product->prices()->first(); $price = $product->prices()->first();
$price->sale_unit_amount = 8000; $price->sale_unit_amount = 80;
$price->save(); $price->save();
$this->assertEquals(10000, $product->getCurrentPrice()); $this->assertEquals(100, $product->getCurrentPrice());
$product->update([ $product->update([
'sale_start' => now()->subHour(), 'sale_start' => now()->subHour(),
]); ]);
$this->assertEquals(8000, $product->getCurrentPrice()); $this->assertEquals(80, $product->getCurrentPrice());
} }
} }

View File

@ -39,7 +39,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_orders() public function shop_facade_can_get_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -53,7 +53,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_order_by_number() public function shop_facade_can_get_order_by_number()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -71,7 +71,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_orders_today() public function shop_facade_can_get_orders_today()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -85,7 +85,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_orders_this_week() public function shop_facade_can_get_orders_this_week()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -99,7 +99,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_orders_this_month() public function shop_facade_can_get_orders_this_month()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -113,7 +113,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_pending_orders() public function shop_facade_can_get_pending_orders()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 2500)->create([ $product = Product::factory()->withPrices(unit_amount: 25.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -127,7 +127,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_calculate_total_revenue() public function shop_facade_can_calculate_total_revenue()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -142,7 +142,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_calculate_revenue_today() public function shop_facade_can_calculate_revenue_today()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 10000)->create([ $product = Product::factory()->withPrices(unit_amount: 100.00)->create([
'manage_stock' => false, 'manage_stock' => false,
]); ]);
@ -157,7 +157,7 @@ class ShopServiceTest extends TestCase
public function shop_facade_can_get_stats() public function shop_facade_can_get_stats()
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$product = Product::factory()->withPrices(unit_amount: 5000)->create([ $product = Product::factory()->withPrices(unit_amount: 50.00)->create([
'manage_stock' => false, 'manage_stock' => false,
'status' => 'published', 'status' => 'published',
]); ]);

View File

@ -79,7 +79,7 @@ class StripeWebhookOrderTest extends TestCase
/** /**
* Create a product for testing * Create a product for testing
*/ */
protected function createProduct(int $price = 10000): Product protected function createProduct(float $price = 100.00): Product
{ {
return Product::factory()->withPrices(unit_amount: $price)->create([ return Product::factory()->withPrices(unit_amount: $price)->create([
'manage_stock' => false, 'manage_stock' => false,
@ -90,7 +90,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_creates_order_payment() public function checkout_session_completed_creates_order_payment()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -108,7 +108,7 @@ class StripeWebhookOrderTest extends TestCase
$this->invokeMethod('handleCheckoutSessionCompleted', [$session]); $this->invokeMethod('handleCheckoutSessionCompleted', [$session]);
$order->refresh(); $order->refresh();
$this->assertEquals(10000, $order->amount_paid); $this->assertEquals(100.00, $order->amount_paid);
$this->assertEquals(OrderStatus::PROCESSING, $order->status); $this->assertEquals(OrderStatus::PROCESSING, $order->status);
} }
@ -116,7 +116,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_logs_payment_note() public function checkout_session_completed_logs_payment_note()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(5000); $product = $this->createProduct(50.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -143,7 +143,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_failed_updates_order_status() public function checkout_session_failed_updates_order_status()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -164,7 +164,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_failed_adds_payment_note() public function checkout_session_failed_adds_payment_note()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(5000); $product = $this->createProduct(50.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -189,7 +189,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_expired_adds_system_note() public function checkout_session_expired_adds_system_note()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(5000); $product = $this->createProduct(50.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -214,7 +214,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_refunded_records_refund_on_order() public function charge_refunded_records_refund_on_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -244,7 +244,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_dispute_created_puts_order_on_hold() public function charge_dispute_created_puts_order_on_hold()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -273,7 +273,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_dispute_created_adds_payment_note() public function charge_dispute_created_adds_payment_note()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -302,7 +302,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_dispute_closed_restores_order_if_won() public function charge_dispute_closed_restores_order_if_won()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -329,7 +329,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_dispute_closed_refunds_order_if_lost() public function charge_dispute_closed_refunds_order_if_lost()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -356,7 +356,7 @@ class StripeWebhookOrderTest extends TestCase
public function refund_created_records_refund_on_order() public function refund_created_records_refund_on_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -383,7 +383,7 @@ class StripeWebhookOrderTest extends TestCase
public function refund_updated_adds_note_to_order() public function refund_updated_adds_note_to_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -410,7 +410,7 @@ class StripeWebhookOrderTest extends TestCase
public function find_order_by_payment_intent_works() public function find_order_by_payment_intent_works()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -428,7 +428,7 @@ class StripeWebhookOrderTest extends TestCase
public function find_order_by_charge_id_works() public function find_order_by_charge_id_works()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -476,7 +476,7 @@ class StripeWebhookOrderTest extends TestCase
public function handler_uses_client_reference_id_as_fallback() public function handler_uses_client_reference_id_as_fallback()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(5000); $product = $this->createProduct(50.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -492,14 +492,14 @@ class StripeWebhookOrderTest extends TestCase
$this->assertTrue($result); $this->assertTrue($result);
$order = $cart->fresh()->order; $order = $cart->fresh()->order;
$this->assertEquals(5000, $order->amount_paid); $this->assertEquals(50.00, $order->amount_paid);
} }
#[Test] #[Test]
public function payment_intent_canceled_adds_note() public function payment_intent_canceled_adds_note()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -525,7 +525,7 @@ class StripeWebhookOrderTest extends TestCase
public function charge_failed_adds_failure_note_to_order() public function charge_failed_adds_failure_note_to_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->checkoutCart(); $cart = $customer->checkoutCart();
@ -559,7 +559,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_creates_order_when_none_exists() public function checkout_session_completed_creates_order_when_none_exists()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
// Add to cart but DON'T call checkoutCart() - simulate checkoutSession() flow // Add to cart but DON'T call checkoutCart() - simulate checkoutSession() flow
$customer->addToCart($product); $customer->addToCart($product);
@ -597,7 +597,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_creates_order_and_records_payment() public function checkout_session_completed_creates_order_and_records_payment()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(15000); $product = $this->createProduct(150.00);
// Add to cart but DON'T call checkoutCart() // Add to cart but DON'T call checkoutCart()
$customer->addToCart($product); $customer->addToCart($product);
@ -622,7 +622,7 @@ class StripeWebhookOrderTest extends TestCase
$order = $cart->order; $order = $cart->order;
$this->assertNotNull($order); $this->assertNotNull($order);
$this->assertEquals(15000, $order->amount_paid); $this->assertEquals(150.00, $order->amount_paid);
$this->assertEquals(OrderStatus::PROCESSING, $order->status); $this->assertEquals(OrderStatus::PROCESSING, $order->status);
} }
@ -630,7 +630,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_creates_order_with_correct_totals() public function checkout_session_completed_creates_order_with_correct_totals()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(7550); $product = $this->createProduct(75.50);
$customer->addToCart($product, 2); // 2 items = 151.00 $customer->addToCart($product, 2); // 2 items = 151.00
$cart = $customer->currentCart(); $cart = $customer->currentCart();
@ -651,15 +651,15 @@ class StripeWebhookOrderTest extends TestCase
$order = $cart->fresh()->order; $order = $cart->fresh()->order;
$this->assertNotNull($order); $this->assertNotNull($order);
// Order total should match cart total (already in cents) // Order total should match cart total (in cents)
$this->assertEquals((int) $cart->getTotal(), $order->amount_total); $this->assertEquals((int) $cart->getTotal() * 100, $order->amount_total);
} }
#[Test] #[Test]
public function checkout_session_completed_adds_payment_note_when_creating_order() public function checkout_session_completed_adds_payment_note_when_creating_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(5000); $product = $this->createProduct(50.00);
$customer->addToCart($product); $customer->addToCart($product);
$cart = $customer->currentCart(); $cart = $customer->currentCart();
@ -685,13 +685,14 @@ class StripeWebhookOrderTest extends TestCase
$paymentNote = $order->notes()->where('type', OrderNote::TYPE_PAYMENT)->first(); $paymentNote = $order->notes()->where('type', OrderNote::TYPE_PAYMENT)->first();
$this->assertNotNull($paymentNote, 'Payment note should be created'); $this->assertNotNull($paymentNote, 'Payment note should be created');
$this->assertStringContainsString('50', $paymentNote->content); $this->assertStringContainsString('50', $paymentNote->content);
$this->assertStringContainsString('Stripe checkout', $paymentNote->content);
} }
#[Test] #[Test]
public function checkout_session_completed_does_not_duplicate_order() public function checkout_session_completed_does_not_duplicate_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(10000); $product = $this->createProduct(100.00);
// Use checkoutCart() which creates an order // Use checkoutCart() which creates an order
$customer->addToCart($product); $customer->addToCart($product);
@ -724,7 +725,7 @@ class StripeWebhookOrderTest extends TestCase
public function checkout_session_completed_without_prior_conversion_creates_order() public function checkout_session_completed_without_prior_conversion_creates_order()
{ {
$customer = User::factory()->create(); $customer = User::factory()->create();
$product = $this->createProduct(20000); $product = $this->createProduct(200.00);
// Add to cart - cart is NOT converted yet (simulates edge case) // Add to cart - cart is NOT converted yet (simulates edge case)
$customer->addToCart($product); $customer->addToCart($product);
@ -750,6 +751,6 @@ class StripeWebhookOrderTest extends TestCase
// Order should exist // Order should exist
$this->assertNotNull($cart->order); $this->assertNotNull($cart->order);
$this->assertEquals(20000, $cart->order->amount_paid); $this->assertEquals(200.00, $cart->order->amount_paid);
} }
} }