id()) ->orderBy('created_at', 'desc') ->get(); return view('custom-orders.index', [ 'customOrders' => $customOrders, ]); } /** * Show custom order creation form */ public function create(): View { $designFee = AppSetting::get('design_fee', 500); return view('custom-orders.create', [ 'printStocks' => PrintStock::all(), 'designFee' => $designFee, ]); } /** * Store custom order and calculate quote */ public function store(Request $request) { error_log('=== CUSTOM ORDER STORE METHOD CALLED (error_log) ==='); error_log('Request method: ' . $request->method()); error_log('Request path: ' . $request->path()); error_log('Request all data: ' . json_encode($request->all())); $userId = auth()->id(); error_log('User ID: ' . ($userId ?? 'NOT AUTHENTICATED')); Log::warning('=== CUSTOM ORDER STORE METHOD CALLED ==='); Log::warning('Request all data', $request->all()); Log::warning('User ID', ['user_id' => auth()->id()]); $validated = $request->validate([ 'type' => 'required|in:wallpaper,mural,fabric', 'length' => 'nullable|numeric|min:0.1', 'width' => 'nullable|numeric|min:0.1', 'height' => 'nullable|numeric|min:0.1', 'print_stock_id' => 'required|exists:print_stocks,id', 'quantity' => 'required|integer|min:1', 'customer_brief' => 'required|string|min:50|max:5000', 'special_instructions' => 'nullable|string|max:1000', 'library_discount' => 'boolean', 'reference_images.*' => 'nullable|file|mimes:jpeg,png,jpg,gif,webp|max:5120', ]); Log::warning('Validation passed', $validated); // Get design fee from settings $designFee = AppSetting::get('design_fee', 500); $libraryDiscountApplied = $request->boolean('library_discount', false); // Deduct library discount if selected $finalDesignFee = $libraryDiscountApplied ? $designFee * 0.8 : $designFee; // Get print stock for material cost calculation $printStock = PrintStock::find($validated['print_stock_id']); // Calculate material cost based on type and dimensions $materialCost = $this->calculateMaterialCost( $validated['type'], $validated['length'] ?? 0, $validated['width'] ?? 0, $validated['height'] ?? 0, $validated['quantity'], $printStock ); // Total cost = material cost + design fee $totalCost = $materialCost + $finalDesignFee; // Deposit is 20% of total, balance is 80% $depositAmount = $totalCost * 0.2; $balanceAmount = $totalCost * 0.8; // Create custom order $customOrder = CustomOrder::create([ 'user_id' => auth()->id(), 'type' => $validated['type'], 'design_fee' => $designFee, 'library_discount_applied' => $libraryDiscountApplied, 'material_cost' => $materialCost, 'total_cost' => $totalCost, 'deposit_amount' => $depositAmount, 'balance_amount' => $balanceAmount, 'customer_brief' => $validated['customer_brief'], ]); // Create specifications CustomOrderSpecification::create([ 'custom_order_id' => $customOrder->id, 'length' => $validated['length'] ?? null, 'width' => $validated['width'] ?? null, 'height' => $validated['height'] ?? null, 'print_stock_id' => $validated['print_stock_id'], 'quantity' => $validated['quantity'], 'special_instructions' => $validated['special_instructions'] ?? null, ]); // Handle file uploads if ($request->hasFile('reference_images')) { foreach ($request->file('reference_images') as $file) { $path = $file->store('custom-orders/references', 'local'); CustomOrderFile::create([ 'custom_order_id' => $customOrder->id, 'file_type' => 'reference_image', 'file_path' => $path, 'original_filename' => $file->getClientOriginalName(), 'file_size' => $file->getSize(), 'mime_type' => $file->getMimeType(), 'uploaded_by' => auth()->id(), ]); } } return redirect()->route('custom-orders.show', $customOrder)->with('success', 'Custom order created successfully. Please review the quote and proceed with deposit payment.'); } /** * Show custom order detail with quote */ public function show(CustomOrder $customOrder): View { // Check authorization if ($customOrder->user_id !== auth()->id()) { abort(403); } return view('custom-orders.show', [ 'customOrder' => $customOrder, ]); } /** * Process deposit payment */ public function depositPayment(Request $request) { Log::info('Deposit payment initiated', [ 'request_data' => $request->all(), 'user_id' => auth()->id(), ]); $validated = $request->validate([ 'custom_order_id' => 'required|exists:custom_orders,id', ]); Log::info('Deposit payment validation passed', $validated); $customOrder = CustomOrder::findOrFail($validated['custom_order_id']); // Check authorization if ($customOrder->user_id !== auth()->id()) { Log::warning('Unauthorized deposit payment attempt', [ 'custom_order_id' => $customOrder->id, 'user_id' => auth()->id(), ]); abort(403); } // Check if already paid if ($customOrder->deposit_status === 'paid') { Log::info('Deposit already paid for custom order', [ 'custom_order_id' => $customOrder->id, ]); return redirect()->route('custom-orders.show', $customOrder) ->with('info', 'Deposit already paid for this order.'); } // Initiate Yoco payment for deposit Log::info('Initiating Yoco payment for custom order deposit', [ 'custom_order_id' => $customOrder->id, 'deposit_amount' => $customOrder->deposit_amount, ]); $yocoResponse = $this->initiateYocoPayment( amount: (int)($customOrder->deposit_amount * 100), // Convert to cents customOrder: $customOrder, orderType: 'custom_deposit', description: "Deposit for Order #{$customOrder->order_number}" ); if (!$yocoResponse) { Log::error('Failed to initiate Yoco payment for custom order deposit', [ 'custom_order_id' => $customOrder->id, ]); return redirect()->route('custom-orders.show', $customOrder) ->with('error', 'Failed to initiate payment. Please try again.'); } Log::info('Yoco payment initiated successfully for custom order deposit', [ 'custom_order_id' => $customOrder->id, 'checkout_url' => $yocoResponse['checkout_url'], ]); return redirect($yocoResponse['checkout_url']); } /** * Handle successful deposit payment */ public function depositSuccess(CustomOrder $customOrder) { // Check authorization if ($customOrder->user_id !== auth()->id()) { abort(403); } // // Update order status // $customOrder->update([ // 'deposit_status' => 'paid', // 'status' => 'submitted', // ]); return view('custom-orders.deposit-success', [ 'customOrder' => $customOrder, ]); } /** * Calculate material cost based on dimensions and print stock */ private function calculateMaterialCost($type, $length, $width, $height, $quantity, $printStock) { $costPerUnit = $printStock->cost_per_meter ?? $printStock->cost_per_m2 ?? 0; if (!$costPerUnit) { return 0; } $cost = 0; if ($type === 'wallpaper' && $length > 0 && $width > 0) { // Area-based: length x width in meters $area = $length * $width; $cost = $area * $costPerUnit * $quantity; } elseif ($type === 'mural' && $width > 0 && $height > 0) { // Area-based: width x height in meters $area = $width * $height; $cost = $area * $costPerUnit * $quantity; } elseif ($type === 'fabric' && $length > 0) { // Linear: length in meters $cost = $length * $costPerUnit * $quantity; } return round($cost, 2); } /** * Initiate Yoco payment */ private function initiateYocoPayment($amount, $customOrder, $orderType, $description) { Log::info('Initiating Yoco payment', [ 'order_id' => $customOrder->uuid, 'order_type' => $orderType, 'amount' => $amount, 'description' => $description, ]); if (!$customOrder) { Log::error('Custom order not found for Yoco payment', [ 'order_id' => $customOrder->uuid ?? 'unknown', ]); return null; } Log::info('Custom order found for Yoco payment', [ 'order_id' => $customOrder->uuid, 'custom_order_data' => $customOrder->toArray(), ]); // Get configuration $secretKey = config('services.yoco.secret_key'); $mode = config('services.yoco.mode'); Log::info('Yoco configuration', [ 'mode' => $mode, 'secret_key_set' => !empty($secretKey) && $secretKey !== 'sk_test_your_key_here', ]); // Check if API key is configured if (empty($secretKey) || $secretKey === 'sk_test_your_key_here') { Log::error('Yoco secret key not configured'); return null; } $baseUrl = $mode === 'live' ? 'https://payments.yoco.com/api/checkouts' : 'https://payments.yoco.com/api/checkouts'; $checkoutData = [ 'amount' => $amount, 'currency' => 'ZAR', 'successUrl' => route('yoco-custom-deposit-success', ['customOrder' => $customOrder->uuid]), 'cancelUrl' => route('custom-orders.show', ['customOrder' => $customOrder->uuid]), 'failureUrl' => route('custom-orders.show', ['customOrder' => $customOrder->uuid]), 'metadata' => [ 'order_uuid' => $customOrder->uuid, 'order_type' => $orderType, 'site' => 'additional_design', 'description' => $description ] ]; Log::info('Yoco checkout data prepared', [ 'order_id' => $customOrder->uuid, 'checkout_data' => $checkoutData, ]); // Make API request to Yoco try { $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $secretKey, 'Content-Type' => 'application/json', ])->post($baseUrl, $checkoutData); if ($response->successful()) { $checkout = $response->json(); $checkoutId = $checkout['id'] ?? null; $redirectUrl = $checkout['redirectUrl'] ?? null; Log::info('Yoco checkout created for custom order', [ 'order_uuid' => $customOrder->uuid, 'checkout_id' => $checkoutId, 'redirect_url' => $redirectUrl, ]); if (!$checkoutId || !$redirectUrl) { Log::error('Invalid Yoco checkout response: missing id or redirectUrl', [ 'order_uuid' => $customOrder->uuid, 'response' => $checkout, ]); throw new \Exception('Invalid Yoco checkout response: missing id or redirectUrl'); } // Persist checkout info to database try { $customOrder->update([ 'yoco_checkout_id' => $checkoutId, 'yoco_redirect_url' => $redirectUrl, 'yoco_checkout_response' => json_encode($checkout), ]); // Reload the model to verify update $customOrder->refresh(); if ($customOrder->yoco_checkout_id) { Log::info('Yoco checkout info saved successfully for custom order', [ 'order_uuid' => $customOrder->uuid, 'yoco_checkout_id' => $customOrder->yoco_checkout_id, ]); } else { Log::warning('Yoco checkout ID not saved after update for custom order', [ 'order_uuid' => $customOrder->uuid, 'order_data' => $customOrder->toArray(), ]); } } catch (\Exception $dbException) { Log::error('Database error while saving Yoco checkout info for custom order', [ 'order_uuid' => $customOrder->uuid, 'error_message' => $dbException->getMessage(), 'error_code' => $dbException->getCode(), 'checkout_id' => $checkoutId, 'redirect_url' => $redirectUrl, ]); throw $dbException; } return [ 'checkout_url' => $redirectUrl, 'checkout_id' => $checkoutId, ]; } else { Log::error('Yoco API Error for custom order', [ 'order_uuid' => $customOrder->uuid, 'status' => $response->status(), 'body' => $response->body() ]); return null; } } catch (\Exception $e) { Log::error('Yoco Payment Exception for custom order', [ 'order_uuid' => $customOrder->uuid, 'message' => $e->getMessage() ]); return null; } } }