diff --git a/README.md b/README.md index 9e248f8..7778dd9 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ of Evolution CMS for seamless and efficient **online commerce**. - [ ] Customer Reviews and Ratings. - [ ] Wishlist and Favorites. - [x] Checkout. +- [x] One Click Checkout. - [ ] Promo Code System. - [x] Plugin events. - [x] sCommerceManagerAddTabEvent. diff --git a/database/migrations/2023_12_27_190234_ecommerce_structure_tables.php b/database/migrations/2023_12_27_190234_ecommerce_structure_tables.php index de1c9d6..644cdd7 100644 --- a/database/migrations/2023_12_27_190234_ecommerce_structure_tables.php +++ b/database/migrations/2023_12_27_190234_ecommerce_structure_tables.php @@ -178,6 +178,7 @@ public function up(): void $table->decimal('cost', 9, 2)->default(0)->comment('Total order amount'); $table->char('currency', 3)->default('USD')->comment('Currency cost this order'); $table->unsignedInteger('status')->default(1)->comment('Order status (1: new)'); + $table->boolean('is_quick')->default(false)->comment('Flag indicating if the order is a quick purchase'); $table->boolean('do_not_call')->default(false)->comment('"Do not call back" option'); $table->text('comment')->nullable()->comment('Comment on the order'); $table->string('lang', 10)->index()->default('base'); diff --git a/docs/pages/index.md b/docs/pages/index.md index 4b3945a..aaaba61 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -72,6 +72,7 @@ of Evolution CMS for seamless and efficient **online commerce**. - [ ] Customer Reviews and Ratings. - [ ] Wishlist and Favorites. - [x] Checkout. +- [x] One Click Checkout. - [ ] Promo Code System. - [x] Plugin events. - [x] sCommerceManagerAddTabEvent. diff --git a/src/Checkout/sCheckout.php b/src/Checkout/sCheckout.php index 5bc4922..0e133da 100644 --- a/src/Checkout/sCheckout.php +++ b/src/Checkout/sCheckout.php @@ -417,7 +417,7 @@ public function processOrder(): array $order = $this->saveOrder(); - evo()->logEvent(0, 1, "Замовлення #{$order->id} успішно створено.", 'sCommerce: Order Created'); + evo()->logEvent(0, 1, "Order #{$order->id} successfully created.", 'sCommerce: Order Created'); Log::info("Order #{$order->id} successfully created.", ['order_id' => $order->id, 'total_cost' => $order->cost, 'user_id' => $order->user_id]); unset($_SESSION['sCheckout'], $_SESSION['sCart']); @@ -440,6 +440,136 @@ public function processOrder(): array } } + /** + * Process the quick order. + * + * This method validates the input data, creates a new order for a quick purchase, + * and saves it in the database. If product is 0, it will fetch products from the cart. + * + * @param array $data The data from the request, including product_id, quantity, user_phone, and/or user_email. + * @return array An array containing the result of the operation with success status and a message. + */ + public static function quickOrder(array $data) + { + $validator = Validator::make($data, [ + 'product' => 'nullable|integer|exists:products,id,' . ((int)$data['productId'] == 0 ? '0' : null), + 'quantity' => 'nullable|integer|min:1', + 'phone' => 'nullable|string|max:20', + 'email' => 'nullable|email|max:255', + ]); + + if (empty($data['phone']) && empty($data['email'])) { + return [ + 'success' => false, + 'message' => 'Either phone number or email is required.', + ]; + } + + if ($validator->fails()) { + return [ + 'success' => false, + 'message' => 'Invalid data provided.', + 'errors' => $validator->errors(), + ]; + } + + $userId = evo()->getLoginUserID('web') ?: evo()->getLoginUserID('mgr'); // Checking if the user is authorized + $user = evo()->getUserInfo($userId ?: 0) ?: []; + $user = array_merge($user, evo()->getUserSettings()); + + $userData = [ + 'id' => $user['id'] ?? 0, + 'name' => $user['fullname'] ?? '', + 'email' => $user['email'] ?? ($data['email'] ?? ''), + 'phone' => $user['phone'] ?? ($data['phone'] ?? ''), + 'address' => [ + 'country' => $user['country'] ?? '', + 'state' => $user['state'] ?? '', + 'city' => $user['city'] ?? '', + 'street' => $user['street'] ?? '', + 'zip' => $user['zip'] ?? '', + ], + ]; + + if (empty($data['productId']) || $data['productId'] == 0) { + $cartData = sCart::getMiniCart(); + if (empty($cartData['items'])) { + return [ + 'success' => false, + 'message' => 'Cart is empty. Cannot create a quick order.', + ]; + } + + $productsData = $cartData['items']; + $cost = $cartData['totalSum']; + } else { + $product = sCommerce::getProduct($data['productId']); + if (!$product) { + return [ + 'success' => false, + 'message' => 'Product not found.', + ]; + } + + $quantity = isset($data['quantity']) && $data['quantity'] > 0 ? (int) $data['quantity'] : 1; + $price = sCommerce::convertPriceNumber($product->price, $product->currency, sCommerce::currentCurrency()); + $cost = $price * $quantity; + + $productsData = [ + [ + 'id' => $product->id, + 'title' => $product->title, + 'link' => $product->link, + 'coverSrc' => $product->coverSrc, + 'category' => $product->category, + 'sku' => $product->sku, + 'inventory' => $product->inventory, + 'price' => $product->price, + 'oldPrice' => $product->oldPrice, + 'quantity' => $quantity, + ] + ]; + } + + do { + $identifier = Str::random(rand(32, 64)); + } while (sOrder::where('identifier', $identifier)->exists()); + + $adminNotes = [ + [ + 'comment' => "Quick order created by user " . implode(' ', [trim($userData['name']), trim($userData['phone']), trim($userData['email'])]) . '.', + 'timestamp' => now(), + 'user_id' => (int)$userData['id'], + ] + ]; + + $order = new sOrder(); + $order->user_id = (int)$userData['id']; + $order->identifier = $identifier; + $order->user_info = json_encode($userData, JSON_UNESCAPED_UNICODE); + $order->products = json_encode($productsData, JSON_UNESCAPED_UNICODE); + $order->cost = $cost; + $order->currency = sCommerce::currentCurrency(); + $order->is_quick = true; + $order->admin_notes = json_encode([ + 'purchase_link' => back()->getTargetUrl(), + ], JSON_UNESCAPED_UNICODE); + $order->save(); + + if ($data['productId'] == 0) { + unset($_SESSION['sCheckout'], $_SESSION['sCart']); + } + + evo()->logEvent(0, 1, "Order #{$order->id} successfully created.", 'sCommerce: Order By Click Created'); + Log::info("Order By Click #{$order->id} successfully created.", ['order_id' => $order->id, 'total_cost' => $order->cost, 'user_id' => $order->user_id]); + + return [ + 'success' => true, + 'message' => __('sCommerce::order.success'), + 'order' => $order, + ]; + } + /** * Register a delivery method. * diff --git a/src/Http/routes.php b/src/Http/routes.php index d04c15f..d8a3c1e 100644 --- a/src/Http/routes.php +++ b/src/Http/routes.php @@ -22,4 +22,8 @@ sCheckout::processOrder(), fn($result) => response()->json($result, $result['success'] === true ? 200 : 422) ))->name('processOrder'); + Route::post('quick-order', fn() => tap( + sCheckout::quickOrder(request()->all()), + fn($result) => response()->json($result, $result['success'] === true ? 200 : 422) + ))->name('quickOrder'); }); diff --git a/src/Models/sOrder.php b/src/Models/sOrder.php index 8a12484..46d13b7 100644 --- a/src/Models/sOrder.php +++ b/src/Models/sOrder.php @@ -49,8 +49,8 @@ class sOrder extends Model const PAYMENT_STATUS_CANCELED = 9; // Оплату скасовано const PAYMENT_STATUS_REJECTED = 10; // Оплату відхилено const PAYMENT_STATUS_AUTHORIZED = 11; // Авторизовано (очікує списання) - const PAYMENT_STATUS_EXPIRED = 12; // Термін дії платежу минув - const PAYMENT_STATUS_PENDING_VERIFICATION = 13; // Очікує верифікації + const PAYMENT_STATUS_PENDING_VERIFICATION = 12; // Очікує верифікації + const PAYMENT_STATUS_EXPIRED = 13; // Термін дії платежу минув /** * Cast attributes to specific types. diff --git a/views/scripts/cart.blade.php b/views/scripts/cart.blade.php index 42afdc6..513b83e 100644 --- a/views/scripts/cart.blade.php +++ b/views/scripts/cart.blade.php @@ -9,6 +9,11 @@ trigger = 'buy'; addToCart(e, productId, quantity, trigger); break; + case Boolean(e.target.closest('[data-sFastBuy]')?.hasAttribute("data-sFastBuy")): + productId = parseInt(e.target.closest('[data-sFastBuy]').getAttribute('data-sFastBuy')); + inputs = e.target.closest('[data-sFastBuy]').parentNode.querySelectorAll('input'); + quickOrder(e, productId, inputs); + break; case Boolean(e.target.closest('[data-sRemove]')?.hasAttribute("data-sRemove")): productId = parseInt(e.target.closest('[data-sRemove]').getAttribute('data-sRemove')); removeFromCart(e, productId); @@ -56,6 +61,35 @@ function addToCart(e, productId, quantity, trigger) { e.target.disabled = false; }); } + function quickOrder(e, productId, inputs) { + e.preventDefault(); + e.target.disabled = true; + + let form = new FormData(); + form.append('productId', productId); + inputs.forEach((input) => { + form.append(input.name, input.value); + }); + + fetch('{{route('sCommerce.quickOrder')}}', { + method: "post", + cache: "no-store", + headers: {"X-Requested-With": "XMLHttpRequest"}, + body: form + }).then((response) => { + return response.json(); + }).then((data) => { + document.dispatchEvent(new CustomEvent('sCommerceAddedQuickOrder', {detail: data})); + e.target.disabled = false; + }).catch(function(error) { + if (error === 'SyntaxError: Unexpected token < in JSON at position 0') { + console.error('Request failed SyntaxError: The response must contain a JSON string.'); + } else { + console.error('Request failed', error, '.'); + } + e.target.disabled = false; + }); + } function removeFromCart(e, productId) { e.preventDefault(); e.target.disabled = true;