@famgia/omnify-laravel 0.0.87 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
- package/dist/chunk-V7LWJ6OM.js.map +1 -0
- package/dist/index.cjs +180 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +5 -1
- package/dist/plugin.cjs +176 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +5 -5
- package/scripts/postinstall.js +29 -36
- package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
- package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
- package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
- package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
- package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
- package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
- package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
- package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
- package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
- package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
- package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
- package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
- package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
- package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
- package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
- package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
- package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
- package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
- package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
- package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
- package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
- package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
- package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
- package/stubs/ai-guides/laravel/README.md.stub +59 -0
- package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
- package/stubs/ai-guides/laravel/controller.md.stub +484 -0
- package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
- package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
- package/stubs/ai-guides/laravel/request.md.stub +450 -0
- package/stubs/ai-guides/laravel/resource.md.stub +516 -0
- package/stubs/ai-guides/laravel/service.md.stub +503 -0
- package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
- package/ai-guides/laravel-guide.md +0 -461
- package/dist/chunk-YVVAJA3T.js.map +0 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# Service Guide
|
|
2
|
+
|
|
3
|
+
> **Related:** [README](./README.md) | [Controller Guide](./controller-guide.md) | [Design Philosophy](./design-philosophy.md)
|
|
4
|
+
|
|
5
|
+
## When to Use Service
|
|
6
|
+
|
|
7
|
+
| ✅ Use Service | ❌ Don't Use Service |
|
|
8
|
+
| ------------------------------------ | -------------------------- |
|
|
9
|
+
| Multi-step business logic | Simple CRUD |
|
|
10
|
+
| External API calls | Single model operation |
|
|
11
|
+
| Complex calculations | Basic queries |
|
|
12
|
+
| Logic reused across controllers | Controller-specific logic |
|
|
13
|
+
| Transaction spanning multiple models | Single model create/update |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Service vs Action
|
|
18
|
+
|
|
19
|
+
| Service | Action |
|
|
20
|
+
| --------------------------------------------- | -------------------------------- |
|
|
21
|
+
| Multiple related operations | Single purpose |
|
|
22
|
+
| Stateful (can have dependencies) | Usually stateless |
|
|
23
|
+
| `OrderService::checkout()`, `processRefund()` | `CreateInvoiceAction::execute()` |
|
|
24
|
+
| Groups related methods | One class = one action |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Service Template
|
|
29
|
+
|
|
30
|
+
```php
|
|
31
|
+
<?php
|
|
32
|
+
|
|
33
|
+
namespace App\Services;
|
|
34
|
+
|
|
35
|
+
use App\Models\Cart;
|
|
36
|
+
use App\Models\Order;
|
|
37
|
+
use App\Models\User;
|
|
38
|
+
use App\Events\OrderPlaced;
|
|
39
|
+
use App\Exceptions\InsufficientInventoryException;
|
|
40
|
+
use App\Exceptions\PaymentFailedException;
|
|
41
|
+
use Illuminate\Support\Facades\DB;
|
|
42
|
+
|
|
43
|
+
class OrderService
|
|
44
|
+
{
|
|
45
|
+
public function __construct(
|
|
46
|
+
private PaymentService $paymentService,
|
|
47
|
+
private InventoryService $inventoryService
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Process checkout: validate → pay → create order → update inventory
|
|
52
|
+
*
|
|
53
|
+
* @throws InsufficientInventoryException
|
|
54
|
+
* @throws PaymentFailedException
|
|
55
|
+
*/
|
|
56
|
+
public function checkout(Cart $cart, User $user, string $paymentMethod): Order
|
|
57
|
+
{
|
|
58
|
+
// 1. Validate business rules
|
|
59
|
+
$this->validateCart($cart);
|
|
60
|
+
$this->inventoryService->validateAvailability($cart->items);
|
|
61
|
+
|
|
62
|
+
// 2. Calculate totals
|
|
63
|
+
$totals = $this->calculateTotals($cart);
|
|
64
|
+
|
|
65
|
+
// 3. Process payment
|
|
66
|
+
$payment = $this->paymentService->charge(
|
|
67
|
+
user: $user,
|
|
68
|
+
amount: $totals['total'],
|
|
69
|
+
method: $paymentMethod
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// 4. Create order (in transaction)
|
|
73
|
+
$order = DB::transaction(function () use ($cart, $user, $totals, $payment) {
|
|
74
|
+
$order = Order::create([
|
|
75
|
+
'user_id' => $user->id,
|
|
76
|
+
'subtotal' => $totals['subtotal'],
|
|
77
|
+
'tax' => $totals['tax'],
|
|
78
|
+
'total' => $totals['total'],
|
|
79
|
+
'payment_id' => $payment->id,
|
|
80
|
+
'status' => 'paid',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
// Create order items
|
|
84
|
+
foreach ($cart->items as $item) {
|
|
85
|
+
$order->items()->create([
|
|
86
|
+
'product_id' => $item->product_id,
|
|
87
|
+
'quantity' => $item->quantity,
|
|
88
|
+
'price' => $item->price,
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update inventory
|
|
93
|
+
$this->inventoryService->decrementStock($cart->items);
|
|
94
|
+
|
|
95
|
+
// Clear cart
|
|
96
|
+
$cart->items()->delete();
|
|
97
|
+
$cart->delete();
|
|
98
|
+
|
|
99
|
+
return $order;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 5. Dispatch events (outside transaction)
|
|
103
|
+
event(new OrderPlaced($order));
|
|
104
|
+
|
|
105
|
+
return $order;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Process refund for an order
|
|
110
|
+
*/
|
|
111
|
+
public function refund(Order $order, ?string $reason = null): Order
|
|
112
|
+
{
|
|
113
|
+
if (!$order->canBeRefunded()) {
|
|
114
|
+
throw new \DomainException('Order cannot be refunded');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return DB::transaction(function () use ($order, $reason) {
|
|
118
|
+
// Refund payment
|
|
119
|
+
$this->paymentService->refund($order->payment_id);
|
|
120
|
+
|
|
121
|
+
// Restore inventory
|
|
122
|
+
$this->inventoryService->restoreStock($order->items);
|
|
123
|
+
|
|
124
|
+
// Update order
|
|
125
|
+
$order->update([
|
|
126
|
+
'status' => 'refunded',
|
|
127
|
+
'refund_reason' => $reason,
|
|
128
|
+
'refunded_at' => now(),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
return $order->fresh();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private function validateCart(Cart $cart): void
|
|
136
|
+
{
|
|
137
|
+
if ($cart->items->isEmpty()) {
|
|
138
|
+
throw new \DomainException('Cart is empty');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private function calculateTotals(Cart $cart): array
|
|
143
|
+
{
|
|
144
|
+
$subtotal = $cart->items->sum(fn ($item) => $item->price * $item->quantity);
|
|
145
|
+
$tax = $subtotal * 0.1; // 10% tax
|
|
146
|
+
|
|
147
|
+
return [
|
|
148
|
+
'subtotal' => $subtotal,
|
|
149
|
+
'tax' => $tax,
|
|
150
|
+
'total' => $subtotal + $tax,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Using Service in Controller
|
|
159
|
+
|
|
160
|
+
```php
|
|
161
|
+
<?php
|
|
162
|
+
|
|
163
|
+
namespace App\Http\Controllers;
|
|
164
|
+
|
|
165
|
+
use App\Http\Requests\OrderStoreRequest;
|
|
166
|
+
use App\Http\Resources\OrderResource;
|
|
167
|
+
use App\Models\Cart;
|
|
168
|
+
use App\Services\OrderService;
|
|
169
|
+
|
|
170
|
+
class OrderController extends Controller
|
|
171
|
+
{
|
|
172
|
+
public function __construct(
|
|
173
|
+
private OrderService $orderService
|
|
174
|
+
) {}
|
|
175
|
+
|
|
176
|
+
public function store(OrderStoreRequest $request): OrderResource
|
|
177
|
+
{
|
|
178
|
+
$order = $this->orderService->checkout(
|
|
179
|
+
cart: Cart::findOrFail($request->cart_id),
|
|
180
|
+
user: $request->user(),
|
|
181
|
+
paymentMethod: $request->payment_method
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return new OrderResource($order);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public function refund(Order $order, RefundRequest $request): OrderResource
|
|
188
|
+
{
|
|
189
|
+
$order = $this->orderService->refund(
|
|
190
|
+
order: $order,
|
|
191
|
+
reason: $request->reason
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
return new OrderResource($order);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Service Rules
|
|
202
|
+
|
|
203
|
+
### 1. Constructor Injection for Dependencies
|
|
204
|
+
|
|
205
|
+
```php
|
|
206
|
+
// ✅ DO: Inject dependencies
|
|
207
|
+
class OrderService
|
|
208
|
+
{
|
|
209
|
+
public function __construct(
|
|
210
|
+
private PaymentService $paymentService,
|
|
211
|
+
private InventoryService $inventoryService
|
|
212
|
+
) {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ❌ DON'T: Create dependencies inside
|
|
216
|
+
class OrderService
|
|
217
|
+
{
|
|
218
|
+
public function checkout()
|
|
219
|
+
{
|
|
220
|
+
$paymentService = new PaymentService(); // Hard to test
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 2. Use Transactions for Multi-Model Operations
|
|
226
|
+
|
|
227
|
+
```php
|
|
228
|
+
// ✅ DO: Wrap in transaction
|
|
229
|
+
return DB::transaction(function () {
|
|
230
|
+
$order = Order::create([...]);
|
|
231
|
+
$order->items()->createMany([...]);
|
|
232
|
+
$this->updateInventory();
|
|
233
|
+
return $order;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ❌ DON'T: Multiple saves without transaction
|
|
237
|
+
$order = Order::create([...]);
|
|
238
|
+
$order->items()->createMany([...]); // If this fails, order exists without items
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3. Dispatch Events Outside Transaction
|
|
242
|
+
|
|
243
|
+
```php
|
|
244
|
+
// ✅ DO: Events after transaction commits
|
|
245
|
+
$order = DB::transaction(function () {
|
|
246
|
+
return Order::create([...]);
|
|
247
|
+
});
|
|
248
|
+
event(new OrderPlaced($order)); // After transaction
|
|
249
|
+
|
|
250
|
+
// ❌ DON'T: Events inside transaction (might fire before commit)
|
|
251
|
+
DB::transaction(function () {
|
|
252
|
+
$order = Order::create([...]);
|
|
253
|
+
event(new OrderPlaced($order)); // Dangerous!
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 4. Throw Domain Exceptions
|
|
258
|
+
|
|
259
|
+
```php
|
|
260
|
+
// ✅ DO: Custom exceptions for business rules
|
|
261
|
+
if (!$order->canBeRefunded()) {
|
|
262
|
+
throw new OrderCannotBeRefundedException($order);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if ($cart->items->isEmpty()) {
|
|
266
|
+
throw new EmptyCartException();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ❌ DON'T: Generic exceptions
|
|
270
|
+
throw new \Exception('Cart is empty'); // Not specific
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 5. Return Models, Not Arrays
|
|
274
|
+
|
|
275
|
+
```php
|
|
276
|
+
// ✅ DO: Return Eloquent models
|
|
277
|
+
public function checkout(...): Order
|
|
278
|
+
{
|
|
279
|
+
// ...
|
|
280
|
+
return $order;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ❌ DON'T: Return arrays (loses type safety)
|
|
284
|
+
public function checkout(...): array
|
|
285
|
+
{
|
|
286
|
+
return ['order' => $order, 'invoice' => $invoice];
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Action Pattern (Alternative)
|
|
293
|
+
|
|
294
|
+
For single-purpose operations that need to be reused:
|
|
295
|
+
|
|
296
|
+
```php
|
|
297
|
+
<?php
|
|
298
|
+
|
|
299
|
+
namespace App\Actions;
|
|
300
|
+
|
|
301
|
+
use App\Models\Order;
|
|
302
|
+
use App\Models\Invoice;
|
|
303
|
+
use App\Jobs\GenerateInvoicePdf;
|
|
304
|
+
|
|
305
|
+
class CreateInvoiceAction
|
|
306
|
+
{
|
|
307
|
+
public function execute(Order $order): Invoice
|
|
308
|
+
{
|
|
309
|
+
$invoice = Invoice::create([
|
|
310
|
+
'order_id' => $order->id,
|
|
311
|
+
'number' => $this->generateNumber(),
|
|
312
|
+
'subtotal' => $order->subtotal,
|
|
313
|
+
'tax' => $order->tax,
|
|
314
|
+
'total' => $order->total,
|
|
315
|
+
'issued_at' => now(),
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
GenerateInvoicePdf::dispatch($invoice);
|
|
319
|
+
|
|
320
|
+
return $invoice;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private function generateNumber(): string
|
|
324
|
+
{
|
|
325
|
+
$year = now()->format('Y');
|
|
326
|
+
$sequence = Invoice::whereYear('created_at', $year)->count() + 1;
|
|
327
|
+
|
|
328
|
+
return sprintf('INV-%s-%05d', $year, $sequence);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Using Action
|
|
334
|
+
|
|
335
|
+
```php
|
|
336
|
+
// In Controller
|
|
337
|
+
public function store(OrderRequest $request, CreateInvoiceAction $createInvoice)
|
|
338
|
+
{
|
|
339
|
+
$order = Order::create($request->validated());
|
|
340
|
+
$createInvoice->execute($order);
|
|
341
|
+
|
|
342
|
+
return new OrderResource($order);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// In Job
|
|
346
|
+
class RecurringBillingJob
|
|
347
|
+
{
|
|
348
|
+
public function handle(CreateInvoiceAction $createInvoice)
|
|
349
|
+
{
|
|
350
|
+
$subscriptions = Subscription::due()->get();
|
|
351
|
+
|
|
352
|
+
foreach ($subscriptions as $subscription) {
|
|
353
|
+
$order = $subscription->createRenewalOrder();
|
|
354
|
+
$createInvoice->execute($order);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// In Console Command
|
|
360
|
+
class CreateMonthlyInvoicesCommand extends Command
|
|
361
|
+
{
|
|
362
|
+
public function handle(CreateInvoiceAction $createInvoice)
|
|
363
|
+
{
|
|
364
|
+
Order::unpaid()->each(fn ($order) => $createInvoice->execute($order));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Service vs Controller Decision
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
┌─────────────────────────────────────────────┐
|
|
375
|
+
│ NEW FEATURE │
|
|
376
|
+
└─────────────────┬───────────────────────────┘
|
|
377
|
+
│
|
|
378
|
+
Is it simple CRUD?
|
|
379
|
+
│
|
|
380
|
+
┌──────────┴──────────┐
|
|
381
|
+
│ │
|
|
382
|
+
YES NO
|
|
383
|
+
│ │
|
|
384
|
+
▼ ▼
|
|
385
|
+
Controller How many steps?
|
|
386
|
+
handles it │
|
|
387
|
+
┌──────────┴──────────┐
|
|
388
|
+
│ │
|
|
389
|
+
1-2 steps 3+ steps
|
|
390
|
+
│ │
|
|
391
|
+
▼ ▼
|
|
392
|
+
Controller Service
|
|
393
|
+
+ Job/Event
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Anti-Patterns
|
|
399
|
+
|
|
400
|
+
```php
|
|
401
|
+
// ❌ DON'T: Service for simple CRUD
|
|
402
|
+
class UserService
|
|
403
|
+
{
|
|
404
|
+
public function create(array $data): User
|
|
405
|
+
{
|
|
406
|
+
return User::create($data); // Pointless wrapper
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ❌ DON'T: Service calling controller methods
|
|
411
|
+
class UserService
|
|
412
|
+
{
|
|
413
|
+
public function __construct(private UserController $controller) {}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ❌ DON'T: HTTP-specific logic in service
|
|
417
|
+
class UserService
|
|
418
|
+
{
|
|
419
|
+
public function create(Request $request) // Don't pass Request
|
|
420
|
+
{
|
|
421
|
+
return User::create($request->validated());
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ✅ DO: Pass validated data
|
|
426
|
+
class UserService
|
|
427
|
+
{
|
|
428
|
+
public function createWithTeam(array $userData, array $teamData): User
|
|
429
|
+
{
|
|
430
|
+
// Complex operation that involves multiple models
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ❌ DON'T: Return response in service
|
|
435
|
+
class OrderService
|
|
436
|
+
{
|
|
437
|
+
public function checkout(): JsonResponse // Don't return HTTP response
|
|
438
|
+
{
|
|
439
|
+
return response()->json($order);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ✅ DO: Return model, let controller format response
|
|
444
|
+
class OrderService
|
|
445
|
+
{
|
|
446
|
+
public function checkout(): Order
|
|
447
|
+
{
|
|
448
|
+
return $order;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Testing Services
|
|
456
|
+
|
|
457
|
+
```php
|
|
458
|
+
<?php
|
|
459
|
+
|
|
460
|
+
namespace Tests\Unit\Services;
|
|
461
|
+
|
|
462
|
+
use App\Services\OrderService;
|
|
463
|
+
use App\Models\Cart;
|
|
464
|
+
use App\Models\User;
|
|
465
|
+
use Tests\TestCase;
|
|
466
|
+
|
|
467
|
+
class OrderServiceTest extends TestCase
|
|
468
|
+
{
|
|
469
|
+
private OrderService $service;
|
|
470
|
+
|
|
471
|
+
protected function setUp(): void
|
|
472
|
+
{
|
|
473
|
+
parent::setUp();
|
|
474
|
+
$this->service = app(OrderService::class);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
public function test_checkout_creates_order(): void
|
|
478
|
+
{
|
|
479
|
+
$user = User::factory()->create();
|
|
480
|
+
$cart = Cart::factory()->hasItems(3)->create(['user_id' => $user->id]);
|
|
481
|
+
|
|
482
|
+
$order = $this->service->checkout(
|
|
483
|
+
cart: $cart,
|
|
484
|
+
user: $user,
|
|
485
|
+
paymentMethod: 'card'
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
$this->assertNotNull($order->id);
|
|
489
|
+
$this->assertEquals('paid', $order->status);
|
|
490
|
+
$this->assertCount(3, $order->items);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
public function test_checkout_fails_with_empty_cart(): void
|
|
494
|
+
{
|
|
495
|
+
$this->expectException(\DomainException::class);
|
|
496
|
+
|
|
497
|
+
$user = User::factory()->create();
|
|
498
|
+
$cart = Cart::factory()->create(['user_id' => $user->id]);
|
|
499
|
+
|
|
500
|
+
$this->service->checkout($cart, $user, 'card');
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|