Skip to content

Services

EasyOTC uses service classes to encapsulate business logic and provide reusable functionality across the application. These services handle complex operations and maintain separation of concerns.

Where this lives

  • Repo: easy-otc-api (GitHub: Eliinova/the-one-otc-api)
  • Service classes: app/Services/ (e.g. CartService.php, CartItemService.php, FileUploadService.php)
  • Tests: tests/Feature/Services/ and tests/Unit/Services/

How to run

bash
# From the easy-otc-api repo root
php artisan tinker          # interactively call services
php artisan test            # run the service test suite

Services are typically consumed from controllers or jobs running on staging (https://stage-api.easyotc.com) or locally (http://localhost:8000). See /access for the full URL list.

Available Services

1. CartService

The CartService manages shopping cart operations including creation, updates, and status management.

Location: app/Services/CartService.php

Key Features

  • Cart Creation and Management: Create, find, and update carts
  • Member Cart Operations: Get or create carts for specific members
  • Status Management: Update cart status (active, completed, abandoned)
  • Expiration Handling: Manage cart expiration dates
  • Prescription Validation: Check prescription requirements
  • Carrier Management: Update cart carrier associations

Methods

Cart Creation and Retrieval
php
// Create a new cart
$cart = $cartService->create([
    'member_id' => $memberId,
    'carrier_id' => $carrierId
]);

// Find cart by UUID
$cart = $cartService->findByUuid($uuid);

// Get or create cart for member
$cart = $cartService->getOrCreateForMember($memberId, $carrierId);

// Create or update cart with additional data
$cart = $cartService->createOrUpdateWithData($memberId, [
    'carrier_id' => $carrierId,
    'notes' => 'Special instructions'
]);
Cart Status Management
php
// Update cart status
$cart = $cartService->updateStatus($cart, 'completed');
Cart Operations
php
// Clear all items from cart
$cartService->clearCart($cart);

// Get cart total
$total = $cartService->getTotal($cart);

// Get cart item count
$itemCount = $cartService->getItemCount($cart);

// Check if cart has expired
$isExpired = $cartService->hasExpired($cart);

// Extend cart expiration
$cart = $cartService->extendExpiration($cart, 30); // 30 days
Prescription Management
php
// Check if cart requires prescription
$requiresPrescription = $cartService->requiresPrescription($cart);

// Get prescription status summary
$summary = $cartService->getPrescriptionStatusSummary($cart);
// Returns: ['total_prescription_items' => 2, 'pending' => 1, 'approved' => 1, 'rejected' => 0]
Carrier Management
php
// Update carrier for cart
$cart = $cartService->updateCarrier($cart, $carrierId);

// Get active cart for member
$cart = $cartService->getActiveCartForMember($memberId);

Usage Example

php
use App\Services\CartService;

class CartController extends Controller
{
    public function __construct(private CartService $cartService) {}

    public function addToCart(Request $request)
    {
        $memberId = $request->user()->member->id;
        
        // Get or create cart for member
        $cart = $this->cartService->getOrCreateForMember($memberId);
        
        // Add items to cart using CartItemService
        // ...
        
        return response()->json([
            'cart' => $cart,
            'total' => $this->cartService->getTotal($cart),
            'item_count' => $this->cartService->getItemCount($cart)
        ]);
    }
}

2. CartItemService

The CartItemService handles individual cart item operations including adding, updating, and removing items.

Location: app/Services/CartItemService.php

Key Features

  • Item Management: Add, update, and remove cart items
  • Quantity Management: Update item quantities with automatic price calculation
  • Prescription Handling: Manage prescription status for items
  • Product Snapshots: Create and manage product snapshots for cart items
  • Batch Operations: Perform operations on multiple items
  • Price Calculations: Handle unit price updates and discount calculations

Methods

Item Management
php
// Add item to cart
$cartItem = $cartItemService->addItem($cart, $product, 2, [
    'notes' => 'Special instructions'
]);

// Update item quantity
$cartItem = $cartItemService->updateQuantity($cartItem, 3);

// Remove item from cart
$cartItemService->removeItem($cartItem);

// Set item quantity (replace existing)
$cartItem = $cartItemService->setQuantity($cartItem, 5);
Item Updates
php
// Update item notes
$cartItem = $cartItemService->updateNotes($cartItem, 'New notes');

// Update prescription status
$cartItem = $cartItemService->updatePrescriptionStatus($cartItem, 'approved');

// Update unit price
$cartItem = $cartItemService->updateUnitPrice($cartItem, 15.99);
Prescription Management
php
// Check if item requires prescription
$requiresPrescription = $cartItemService->requiresPrescription($cartItem);

// Get items by prescription status
$pendingItems = $cartItemService->getItemsByPrescriptionStatus($cart, 'pending');

// Get pending prescription items
$pendingItems = $cartItemService->getPendingPrescriptionItems($cart);

// Batch update prescription status
$updatedCount = $cartItemService->batchUpdatePrescriptionStatus([1, 2, 3], 'approved');
Product Snapshots
php
// Get product snapshot
$snapshot = $cartItemService->getProductSnapshot($cartItem);

// Calculate item total with discount
$total = $cartItemService->calculateTotal($cartItem, 10); // 10% discount
Item Retrieval
php
// Get cart item by product
$cartItem = $cartItemService->getCartItemByProduct($cart, $productId);

Usage Example

php
use App\Services\CartItemService;

class CartItemController extends Controller
{
    public function __construct(private CartItemService $cartItemService) {}

    public function addToCart(Request $request)
    {
        $cart = $request->user()->member->activeCart;
        $product = Product::findOrFail($request->product_id);
        $quantity = $request->quantity ?? 1;

        $cartItem = $this->cartItemService->addItem($cart, $product, $quantity, [
            'notes' => $request->notes
        ]);

        return response()->json([
            'cart_item' => $cartItem,
            'message' => 'Item added to cart successfully'
        ]);
    }

    public function updateQuantity(Request $request, CartItem $cartItem)
    {
        $quantity = $request->quantity;
        
        if ($quantity <= 0) {
            $this->cartItemService->removeItem($cartItem);
            return response()->json(['message' => 'Item removed from cart']);
        }

        $cartItem = $this->cartItemService->updateQuantity($cartItem, $quantity);
        
        return response()->json([
            'cart_item' => $cartItem,
            'message' => 'Quantity updated successfully'
        ]);
    }
}

3. FileUploadService

The FileUploadService handles file uploads to S3 storage with support for both regular file uploads and base64 data.

Location: app/Services/FileUploadService.php

Key Features

  • S3 Integration: Upload files directly to Amazon S3
  • Multiple Upload Types: Support for regular files and base64 data
  • Unique Filename Generation: Automatic unique filename creation
  • URL Generation: Generate public URLs for uploaded files
  • File Deletion: Remove files from S3 storage
  • Error Handling: Comprehensive error handling and logging

Methods

File Upload
php
// Store regular file upload
$url = $fileUploadService->store($request->file('image'), 'products');

// Store base64 image data
$url = $fileUploadService->storeFromBase64(
    $base64Data,
    'products',
    'jpg',
    'Product Name'
);
File Management
php
// Delete file from S3
$deleted = $fileUploadService->delete($url);

// Generate S3 URL for a path
$url = $fileUploadService->generateUrl($path);

Usage Example

php
use App\Services\FileUploadService;

class ProductController extends Controller
{
    public function __construct(private FileUploadService $fileUploadService) {}

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string',
            'image' => 'nullable|image|max:2048',
            'base64_image' => 'nullable|string'
        ]);

        $productData = $request->only(['name', 'description', 'price']);

        // Handle regular file upload
        if ($request->hasFile('image')) {
            $imageUrl = $this->fileUploadService->store(
                $request->file('image'),
                'products'
            );
            $productData['image_url'] = $imageUrl;
        }

        // Handle base64 image upload
        if ($request->base64_image) {
            $imageUrl = $this->fileUploadService->storeFromBase64(
                $request->base64_image,
                'products',
                'jpg',
                $request->name
            );
            $productData['image_url'] = $imageUrl;
        }

        $product = Product::create($productData);

        return response()->json([
            'product' => $product,
            'message' => 'Product created successfully'
        ]);
    }

    public function update(Request $request, Product $product)
    {
        // Handle image update
        if ($request->hasFile('image')) {
            // Delete old image if exists
            if ($product->image_url) {
                $this->fileUploadService->delete($product->image_url);
            }

            // Upload new image
            $imageUrl = $this->fileUploadService->store(
                $request->file('image'),
                'products'
            );
            
            $product->update(['image_url' => $imageUrl]);
        }

        return response()->json([
            'product' => $product,
            'message' => 'Product updated successfully'
        ]);
    }
}

Service Integration

Dependency Injection

Services can be injected into controllers and other classes using Laravel's dependency injection:

php
class CartController extends Controller
{
    public function __construct(
        private CartService $cartService,
        private CartItemService $cartItemService
    ) {}

    public function addToCart(Request $request)
    {
        $cart = $this->cartService->getOrCreateForMember($request->user()->member->id);
        $product = Product::findOrFail($request->product_id);
        
        $cartItem = $this->cartItemService->addItem($cart, $product, $request->quantity);
        
        return response()->json([
            'cart' => $this->cartService->getCartWithItems($cart),
            'total' => $this->cartService->getTotal($cart)
        ]);
    }
}

Service Providers

Services can be registered in service providers for additional configuration:

php
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(CartService::class, function ($app) {
        return new CartService();
    });

    $this->app->singleton(CartItemService::class, function ($app) {
        return new CartItemService();
    });

    $this->app->singleton(FileUploadService::class, function ($app) {
        return new FileUploadService();
    });
}

Best Practices

1. Service Design

  • Keep services focused on a single responsibility
  • Use dependency injection for better testability
  • Implement proper error handling and validation
  • Document all public methods with clear descriptions

2. Cart Management

  • Always validate cart ownership before operations
  • Handle cart expiration gracefully
  • Implement proper prescription validation workflows
  • Use transactions for critical cart operations

3. File Uploads

  • Validate file types and sizes before upload
  • Implement proper error handling for upload failures
  • Clean up old files when updating
  • Use appropriate S3 permissions and visibility settings

4. Error Handling

  • Implement comprehensive error handling in all services
  • Log errors appropriately for debugging
  • Return meaningful error messages to clients
  • Handle edge cases gracefully

Testing Services

Services can be tested using Laravel's testing framework:

php
class CartServiceTest extends TestCase
{
    use RefreshDatabase;

    public function test_can_create_cart_for_member()
    {
        $member = Member::factory()->create();
        $cartService = new CartService();

        $cart = $cartService->createOrUpdate($member->id);

        $this->assertInstanceOf(Cart::class, $cart);
        $this->assertEquals($member->id, $cart->member_id);
        $this->assertEquals('active', $cart->status);
    }

    public function test_can_add_item_to_cart()
    {
        $cart = Cart::factory()->create();
        $product = Product::factory()->create();
        $cartItemService = new CartItemService();

        $cartItem = $cartItemService->addItem($cart, $product, 2);

        $this->assertInstanceOf(CartItem::class, $cartItem);
        $this->assertEquals(2, $cartItem->quantity);
        $this->assertEquals($product->price * 2, $cartItem->total_price);
    }
}

Configuration

S3 Configuration

The FileUploadService requires proper S3 configuration in config/filesystems.php:

php
's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],

Environment Variables

Required environment variables for file uploads:

env
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_URL=https://your-bucket.s3.amazonaws.com