feat: create new order
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Adapters\OcModelCatalogProductAdapter;
|
use App\Adapters\OcModelCatalogProductAdapter;
|
||||||
use App\ApplicationFactory;
|
use App\ApplicationFactory;
|
||||||
|
use App\Decorators\OcRegistryDecorator;
|
||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
use Cart\Currency;
|
use Cart\Currency;
|
||||||
use Cart\Tax;
|
use Cart\Tax;
|
||||||
@@ -25,6 +26,7 @@ class Controllerextensiontgshophandle extends Controller
|
|||||||
$app = ApplicationFactory::create([
|
$app = ApplicationFactory::create([
|
||||||
'oc_config_tax' => $this->config->get('config_tax'),
|
'oc_config_tax' => $this->config->get('config_tax'),
|
||||||
'oc_default_currency' => $this->config->get('config_currency'),
|
'oc_default_currency' => $this->config->get('config_currency'),
|
||||||
|
'oc_customer_group_id' => $this->config->get('config_customer_group_id'),
|
||||||
'timezone' => $this->config->get('config_timezone', 'UTC'),
|
'timezone' => $this->config->get('config_timezone', 'UTC'),
|
||||||
'language_id' => (int) $this->config->get('config_language_id'),
|
'language_id' => (int) $this->config->get('config_language_id'),
|
||||||
'shop_base_url' => HTTPS_SERVER,
|
'shop_base_url' => HTTPS_SERVER,
|
||||||
@@ -61,222 +63,18 @@ class Controllerextensiontgshophandle extends Controller
|
|||||||
return new ImageTool(DIR_IMAGE, HTTPS_SERVER);
|
return new ImageTool(DIR_IMAGE, HTTPS_SERVER);
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->bind(\Cart\Cart::class, function () {
|
$app->bind(Cart::class, function () {
|
||||||
return $this->cart;
|
return $this->cart;
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->bind(DB::class, function () {
|
$app->bind(OcRegistryDecorator::class, function () {
|
||||||
return $this->db;
|
return new OcRegistryDecorator($this->registry);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->load->model('checkout/order');
|
$this->load->model('checkout/order');
|
||||||
|
|
||||||
$app->bind('model_checkout_order', function () {
|
|
||||||
return $this->model_checkout_order;
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->session->data['language'] = $this->config->get('config_language');
|
$this->session->data['language'] = $this->config->get('config_language');
|
||||||
|
|
||||||
$app->bootAndHandleRequest();
|
$app->bootAndHandleRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cart() {
|
|
||||||
$this->load->language('checkout/cart');
|
|
||||||
|
|
||||||
if ($this->cart->hasProducts()) {
|
|
||||||
if (!$this->cart->hasStock() && (!$this->config->get('config_stock_checkout') || $this->config->get('config_stock_warning'))) {
|
|
||||||
$data['error_warning'] = $this->language->get('error_stock');
|
|
||||||
} elseif (isset($this->session->data['error'])) {
|
|
||||||
$data['error_warning'] = $this->session->data['error'];
|
|
||||||
|
|
||||||
unset($this->session->data['error']);
|
|
||||||
} else {
|
|
||||||
$data['error_warning'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->config->get('config_customer_price') && !$this->customer->isLogged()) {
|
|
||||||
$data['attention'] = sprintf($this->language->get('text_login'), $this->url->link('account/login'), $this->url->link('account/register'));
|
|
||||||
} else {
|
|
||||||
$data['attention'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this->session->data['success'])) {
|
|
||||||
$data['success'] = $this->session->data['success'];
|
|
||||||
|
|
||||||
unset($this->session->data['success']);
|
|
||||||
} else {
|
|
||||||
$data['success'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->config->get('config_cart_weight')) {
|
|
||||||
$data['weight'] = $this->weight->format($this->cart->getWeight(), $this->config->get('config_weight_class_id'), $this->language->get('decimal_point'), $this->language->get('thousand_point'));
|
|
||||||
} else {
|
|
||||||
$data['weight'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load->model('tool/image');
|
|
||||||
$this->load->model('tool/upload');
|
|
||||||
|
|
||||||
$data['products'] = array();
|
|
||||||
|
|
||||||
$products = $this->cart->getProducts();
|
|
||||||
|
|
||||||
foreach ($products as $product) {
|
|
||||||
$product_total = 0;
|
|
||||||
|
|
||||||
foreach ($products as $product_2) {
|
|
||||||
if ($product_2['product_id'] == $product['product_id']) {
|
|
||||||
$product_total += $product_2['quantity'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($product['minimum'] > $product_total) {
|
|
||||||
$data['error_warning'] = sprintf($this->language->get('error_minimum'), $product['name'], $product['minimum']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($product['image']) {
|
|
||||||
$image = $this->model_tool_image->resize($product['image'], $this->config->get('theme_' . $this->config->get('config_theme') . '_image_cart_width'), $this->config->get('theme_' . $this->config->get('config_theme') . '_image_cart_height'));
|
|
||||||
} else {
|
|
||||||
$image = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$option_data = array();
|
|
||||||
|
|
||||||
foreach ($product['option'] as $option) {
|
|
||||||
if ($option['type'] != 'file') {
|
|
||||||
$value = $option['value'];
|
|
||||||
} else {
|
|
||||||
$upload_info = $this->model_tool_upload->getUploadByCode($option['value']);
|
|
||||||
|
|
||||||
if ($upload_info) {
|
|
||||||
$value = $upload_info['name'];
|
|
||||||
} else {
|
|
||||||
$value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$option_data[] = array(
|
|
||||||
'name' => $option['name'],
|
|
||||||
'value' => (utf8_strlen($value) > 20 ? utf8_substr($value, 0, 20) . '..' : $value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display prices
|
|
||||||
if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) {
|
|
||||||
$unit_price = $this->tax->calculate($product['price'], $product['tax_class_id'], $this->config->get('config_tax'));
|
|
||||||
|
|
||||||
$price = $this->currency->format($unit_price, $this->session->data['currency']);
|
|
||||||
$total = $this->currency->format($unit_price * $product['quantity'], $this->session->data['currency']);
|
|
||||||
} else {
|
|
||||||
$price = false;
|
|
||||||
$total = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$recurring = '';
|
|
||||||
|
|
||||||
if ($product['recurring']) {
|
|
||||||
$frequencies = array(
|
|
||||||
'day' => $this->language->get('text_day'),
|
|
||||||
'week' => $this->language->get('text_week'),
|
|
||||||
'semi_month' => $this->language->get('text_semi_month'),
|
|
||||||
'month' => $this->language->get('text_month'),
|
|
||||||
'year' => $this->language->get('text_year')
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($product['recurring']['trial']) {
|
|
||||||
$recurring = sprintf($this->language->get('text_trial_description'), $this->currency->format($this->tax->calculate($product['recurring']['trial_price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['trial_cycle'], $frequencies[$product['recurring']['trial_frequency']], $product['recurring']['trial_duration']) . ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($product['recurring']['duration']) {
|
|
||||||
$recurring .= sprintf($this->language->get('text_payment_description'), $this->currency->format($this->tax->calculate($product['recurring']['price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['cycle'], $frequencies[$product['recurring']['frequency']], $product['recurring']['duration']);
|
|
||||||
} else {
|
|
||||||
$recurring .= sprintf($this->language->get('text_payment_cancel'), $this->currency->format($this->tax->calculate($product['recurring']['price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['cycle'], $frequencies[$product['recurring']['frequency']], $product['recurring']['duration']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['products'][] = array(
|
|
||||||
'cart_id' => (int)$product['cart_id'],
|
|
||||||
'thumb' => $image,
|
|
||||||
'name' => $product['name'],
|
|
||||||
'model' => $product['model'],
|
|
||||||
'option' => $option_data,
|
|
||||||
'recurring' => $recurring,
|
|
||||||
'quantity' => (int)$product['quantity'],
|
|
||||||
'stock' => $product['stock'] ? true : !(!$this->config->get('config_stock_checkout') || $this->config->get('config_stock_warning')),
|
|
||||||
'reward' => ($product['reward'] ? sprintf($this->language->get('text_points'), $product['reward']) : ''),
|
|
||||||
'price' => $price,
|
|
||||||
'total' => $total,
|
|
||||||
'href' => $this->url->link('product/product', 'product_id=' . $product['product_id'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Totals
|
|
||||||
$this->load->model('setting/extension');
|
|
||||||
|
|
||||||
$totals = array();
|
|
||||||
$taxes = $this->cart->getTaxes();
|
|
||||||
$total = 0;
|
|
||||||
|
|
||||||
// Because __call can not keep var references so we put them into an array.
|
|
||||||
$total_data = array(
|
|
||||||
'totals' => &$totals,
|
|
||||||
'taxes' => &$taxes,
|
|
||||||
'total' => &$total
|
|
||||||
);
|
|
||||||
|
|
||||||
$sort_order = array();
|
|
||||||
|
|
||||||
$results = $this->model_setting_extension->getExtensions('total');
|
|
||||||
|
|
||||||
foreach ($results as $key => $value) {
|
|
||||||
$sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order');
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($sort_order, SORT_ASC, $results);
|
|
||||||
|
|
||||||
foreach ($results as $result) {
|
|
||||||
if ($this->config->get('total_' . $result['code'] . '_status')) {
|
|
||||||
$this->load->model('extension/total/' . $result['code']);
|
|
||||||
|
|
||||||
// We have to put the totals in an array so that they pass by reference.
|
|
||||||
$this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sort_order = array();
|
|
||||||
|
|
||||||
foreach ($totals as $key => $value) {
|
|
||||||
$sort_order[$key] = $value['sort_order'];
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($sort_order, SORT_ASC, $totals);
|
|
||||||
|
|
||||||
$data['totals'] = array();
|
|
||||||
|
|
||||||
foreach ($totals as $total) {
|
|
||||||
$data['totals'][] = array(
|
|
||||||
'title' => $total['title'],
|
|
||||||
'text' => $this->currency->format($total['value'], $this->session->data['currency'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['total_products_count'] = $this->cart->countProducts();
|
|
||||||
} else {
|
|
||||||
$data['text_error'] = $this->language->get('text_empty');
|
|
||||||
$data['totals'] = [];
|
|
||||||
$data['total'] = 0;
|
|
||||||
$data['products'] = [];
|
|
||||||
$data['total_products_count'] = 0;
|
|
||||||
unset($this->session->data['success']);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_response_code(200);
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
|
||||||
header('Access-Control-Allow-Methods: GET, POST');
|
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
|
||||||
header('Access-Control-Allow-Credentials: true');
|
|
||||||
echo json_encode($data);
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"psr/container": "^2.0",
|
"psr/container": "^2.0",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"intervention/image": "^2.7"
|
"intervention/image": "^2.7",
|
||||||
|
"rakit/validation": "^1.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"roave/security-advisories": "dev-latest"
|
"roave/security-advisories": "dev-latest"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "4c731e75f55435f8b300c557fdfabfed",
|
"content-hash": "0862025c427a0e17dfde4fb54820c5c0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/psr7",
|
"name": "guzzlehttp/psr7",
|
||||||
@@ -367,6 +367,52 @@
|
|||||||
},
|
},
|
||||||
"time": "2023-04-04T09:54:51+00:00"
|
"time": "2023-04-04T09:54:51+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "rakit/validation",
|
||||||
|
"version": "v1.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rakit/validation.git",
|
||||||
|
"reference": "ff003a35cdf5030a5f2482299f4c93f344a35b29"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/rakit/validation/zipball/ff003a35cdf5030a5f2482299f4c93f344a35b29",
|
||||||
|
"reference": "ff003a35cdf5030a5f2482299f4c93f344a35b29",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": ">=7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-coveralls/php-coveralls": "^2.2",
|
||||||
|
"phpunit/phpunit": "^6.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Rakit\\Validation\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Muhammad Syifa",
|
||||||
|
"email": "emsifa@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Laravel like standalone validation library",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/rakit/validation/issues",
|
||||||
|
"source": "https://github.com/rakit/validation/tree/v1.4.0"
|
||||||
|
},
|
||||||
|
"time": "2020-08-27T05:07:01+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ralouphie/getallheaders",
|
"name": "ralouphie/getallheaders",
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class Request
|
|||||||
array $files,
|
array $files,
|
||||||
array $server,
|
array $server,
|
||||||
string $content = null
|
string $content = null
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
$this->query = $query;
|
$this->query = $query;
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->cookies = $cookies;
|
$this->cookies = $cookies;
|
||||||
@@ -69,4 +70,9 @@ class Request
|
|||||||
|
|
||||||
return $this->query[$key] ?? $default;
|
return $this->query[$key] ?? $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getClientIp(): ?string
|
||||||
|
{
|
||||||
|
return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,4 +31,10 @@ interface ConnectionInterface
|
|||||||
public function lastInsertId($name = null): int;
|
public function lastInsertId($name = null): int;
|
||||||
|
|
||||||
public function getVersion(): string;
|
public function getVersion(): string;
|
||||||
|
|
||||||
|
public function insert(string $table, array $data): bool;
|
||||||
|
|
||||||
|
public function transaction(callable $callback): void;
|
||||||
|
|
||||||
|
public function getLastError(): ?array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use RuntimeException;
|
|||||||
class MySqlConnection implements ConnectionInterface
|
class MySqlConnection implements ConnectionInterface
|
||||||
{
|
{
|
||||||
private $pdo;
|
private $pdo;
|
||||||
|
private ?array $lastError = null;
|
||||||
|
|
||||||
public function __construct(PDO $pdo)
|
public function __construct(PDO $pdo)
|
||||||
{
|
{
|
||||||
@@ -87,7 +88,21 @@ class MySqlConnection implements ConnectionInterface
|
|||||||
|
|
||||||
public function statement(string $sql, array $bindings = []): bool
|
public function statement(string $sql, array $bindings = []): bool
|
||||||
{
|
{
|
||||||
return $this->pdo->prepare($sql)->execute($bindings);
|
$statement = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
if (! $statement) {
|
||||||
|
$this->lastError = $this->pdo->errorInfo();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $statement->execute($bindings);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
$this->lastError = $statement->errorInfo();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function beginTransaction(): bool
|
public function beginTransaction(): bool
|
||||||
@@ -128,4 +143,30 @@ class MySqlConnection implements ConnectionInterface
|
|||||||
->query('SELECT VERSION()')
|
->query('SELECT VERSION()')
|
||||||
->fetch(PDO::FETCH_COLUMN);
|
->fetch(PDO::FETCH_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function insert(string $table, array $data): bool
|
||||||
|
{
|
||||||
|
$placeholders = implode(',', array_fill(0, count($data), '?'));
|
||||||
|
$columns = implode(',', array_map(static fn ($key) => "`${key}`", array_keys($data)));
|
||||||
|
$sql = sprintf('INSERT INTO `%s` (%s) VALUES (%s)', $table, $columns, $placeholders);
|
||||||
|
|
||||||
|
return $this->statement($sql, array_values($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transaction(callable $callback): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->beginTransaction();
|
||||||
|
$callback();
|
||||||
|
$this->commitTransaction();
|
||||||
|
} catch (\Exception $exception) {
|
||||||
|
$this->rollBackTransaction();
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastError(): ?array
|
||||||
|
{
|
||||||
|
return $this->lastError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Decorators;
|
||||||
|
|
||||||
|
use Cart\Cart;
|
||||||
|
use Loader;
|
||||||
|
use Registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Loader $load
|
||||||
|
* @property Cart $cart
|
||||||
|
*/
|
||||||
|
class OcRegistryDecorator
|
||||||
|
{
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
public function __construct(Registry $registry)
|
||||||
|
{
|
||||||
|
$this->registry = $registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($key)
|
||||||
|
{
|
||||||
|
return $this->registry->get($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __set($key, $value)
|
||||||
|
{
|
||||||
|
$this->registry->set($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __isset($key)
|
||||||
|
{
|
||||||
|
return $this->registry->has($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Handlers;
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
use App\Services\CartService;
|
||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
@@ -11,12 +12,22 @@ class CartHandler
|
|||||||
{
|
{
|
||||||
private Cart $cart;
|
private Cart $cart;
|
||||||
private ImageToolInterface $imageTool;
|
private ImageToolInterface $imageTool;
|
||||||
|
private CartService $cartService;
|
||||||
|
|
||||||
public function __construct(Cart $cart, \DB $database, ImageToolInterface $imageTool)
|
public function __construct(Cart $cart, ImageToolInterface $imageTool, CartService $cartService)
|
||||||
{
|
{
|
||||||
$this->cart = $cart;
|
$this->cart = $cart;
|
||||||
$this->database = $database;
|
|
||||||
$this->imageTool = $imageTool;
|
$this->imageTool = $imageTool;
|
||||||
|
$this->cartService = $cartService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
$items = $this->cartService->getCart();
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => $items,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkout(Request $request): JsonResponse
|
public function checkout(Request $request): JsonResponse
|
||||||
@@ -43,15 +54,4 @@ class CartHandler
|
|||||||
'data' => $items,
|
'data' => $items,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getProducts(): array
|
|
||||||
{
|
|
||||||
$products = $this->cart->getProducts();
|
|
||||||
|
|
||||||
foreach ($products as &$product) {
|
|
||||||
$product['thumb'] = $this->imageTool->resize($product['image'], 100, 100, 'placeholder.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $products;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Handlers;
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
|
||||||
|
|
||||||
class OrderCreateHandler
|
|
||||||
{
|
|
||||||
private ConnectionInterface $database;
|
|
||||||
|
|
||||||
public function __construct(ConnectionInterface $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
$storeId = 0;
|
|
||||||
$storeName = 'Ваш магазин';
|
|
||||||
|
|
||||||
$sql = <<<SQL
|
|
||||||
INSERT INTO oc_order
|
|
||||||
(
|
|
||||||
store_id,
|
|
||||||
store_name,
|
|
||||||
firstname,
|
|
||||||
lastname,
|
|
||||||
shipping_code,
|
|
||||||
total,
|
|
||||||
order_status_id,
|
|
||||||
currency_code,
|
|
||||||
currency_value,
|
|
||||||
date_added,
|
|
||||||
date_modified
|
|
||||||
)
|
|
||||||
VALUES
|
|
||||||
(
|
|
||||||
$storeId,
|
|
||||||
'$storeName',
|
|
||||||
'John',
|
|
||||||
'Doe',
|
|
||||||
'flat.flat',
|
|
||||||
99.9999,
|
|
||||||
1,
|
|
||||||
'RUB',
|
|
||||||
1,
|
|
||||||
'$now',
|
|
||||||
'$now'
|
|
||||||
)
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
$result = $this->database->statement($sql);
|
|
||||||
|
|
||||||
|
|
||||||
return new JsonResponse([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
159
module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/OrderHandler.php
Executable file
159
module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/OrderHandler.php
Executable file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
use App\Decorators\OcRegistryDecorator;
|
||||||
|
use App\Services\CartService;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
|
use Openguru\OpenCartFramework\Http\Response;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
use Rakit\Validation\Validator;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class OrderHandler
|
||||||
|
{
|
||||||
|
private ConnectionInterface $database;
|
||||||
|
private CartService $cartService;
|
||||||
|
private OcRegistryDecorator $oc;
|
||||||
|
private Settings $settings;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ConnectionInterface $database,
|
||||||
|
CartService $cartService,
|
||||||
|
OcRegistryDecorator $registry,
|
||||||
|
Settings $settings
|
||||||
|
) {
|
||||||
|
$this->database = $database;
|
||||||
|
$this->cartService = $cartService;
|
||||||
|
$this->oc = $registry;
|
||||||
|
$this->settings = $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = new Validator();
|
||||||
|
|
||||||
|
$validation = $validator->make($request->json(), [
|
||||||
|
'firstName' => 'required',
|
||||||
|
'lastName' => 'required',
|
||||||
|
'email' => 'required|email',
|
||||||
|
'phone' => 'required',
|
||||||
|
'address' => 'required',
|
||||||
|
'comment' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$validation->validate();
|
||||||
|
|
||||||
|
if ($validation->fails()) {
|
||||||
|
$errors = $validation->errors();
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => $errors->firstOfAll(),
|
||||||
|
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
$storeId = 0;
|
||||||
|
$storeName = 'Ваш магазин';
|
||||||
|
|
||||||
|
$cart = $this->cartService->getCart();
|
||||||
|
$total = $cart['total'] ?? 0;
|
||||||
|
$orderStatusId = 1;
|
||||||
|
$products = $cart['products'] ?? [];
|
||||||
|
$totals = $cart['totals'] ?? [];
|
||||||
|
$customerGroupId = $this->settings->get('oc_customer_group_id');
|
||||||
|
|
||||||
|
$languageId = $this->oc->config->get('config_language_id');
|
||||||
|
$currencyId = $this->oc->currency->getId($this->oc->session->data['currency']);
|
||||||
|
$currencyCode = $this->oc->session->data['currency'];
|
||||||
|
$currencyValue = $this->oc->currency->getValue($this->oc->session->data['currency']);
|
||||||
|
|
||||||
|
$orderData = [
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'store_name' => $storeName,
|
||||||
|
'firstname' => $request->json('firstName'),
|
||||||
|
'lastname' => $request->json('lastName'),
|
||||||
|
'email' => $request->json('email'),
|
||||||
|
'telephone' => $request->json('phone'),
|
||||||
|
'comment' => $request->json('comment'),
|
||||||
|
'shipping_address_1' => $request->json('address'),
|
||||||
|
'total' => $total,
|
||||||
|
'order_status_id' => $orderStatusId,
|
||||||
|
'ip' => $request->getClientIp(),
|
||||||
|
'date_added' => $now,
|
||||||
|
'date_modified' => $now,
|
||||||
|
'language_id' => $languageId,
|
||||||
|
'currency_id' => $currencyId,
|
||||||
|
'currency_code' => $currencyCode,
|
||||||
|
'currency_value' => $currencyValue,
|
||||||
|
'customer_group_id' => $customerGroupId,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->database->transaction(
|
||||||
|
function () use ($orderData, $products, $totals, $orderStatusId, $now) {
|
||||||
|
$success = $this->database->insert(db_table('order'), $orderData);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
[, $error] = $this->database->getLastError();
|
||||||
|
throw new RuntimeException("Failed to insert row into order. Error: $error");
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderId = $this->database->lastInsertId();
|
||||||
|
|
||||||
|
// Insert products
|
||||||
|
foreach ($products as $product) {
|
||||||
|
$success = $this->database->insert(db_table('order_product'), [
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'product_id' => $product['product_id'],
|
||||||
|
'name' => $product['name'],
|
||||||
|
'model' => $product['model'],
|
||||||
|
'quantity' => $product['quantity'],
|
||||||
|
'price' => $product['price_numeric'],
|
||||||
|
'total' => $product['total_numeric'],
|
||||||
|
'reward' => $product['reward_numeric'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
[, $error] = $this->database->getLastError();
|
||||||
|
throw new RuntimeException("Failed to insert row into order_product. Error: $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert totals
|
||||||
|
foreach ($totals as $total) {
|
||||||
|
$success = $this->database->insert(db_table('order_total'), [
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'code' => $total['code'],
|
||||||
|
'title' => $total['title'],
|
||||||
|
'value' => $total['value'],
|
||||||
|
'sort_order' => $total['sort_order'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
[, $error] = $this->database->getLastError();
|
||||||
|
throw new RuntimeException("Failed to insert row into order_total. Error: $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert history
|
||||||
|
$success = $this->database->insert(db_table('order_history'), [
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'order_status_id' => $orderStatusId,
|
||||||
|
'notify' => 0,
|
||||||
|
'comment' => 'Заказ оформлен через Telegram Mini App',
|
||||||
|
'date_added' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
[, $error] = $this->database->getLastError();
|
||||||
|
throw new RuntimeException("Failed to insert row into order_history. Error: $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->cartService->flush();
|
||||||
|
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Decorators\OcRegistryDecorator;
|
||||||
|
use Cart\Cart;
|
||||||
|
|
||||||
|
class CartService
|
||||||
|
{
|
||||||
|
private OcRegistryDecorator $oc;
|
||||||
|
private Cart $cart;
|
||||||
|
|
||||||
|
public function __construct(OcRegistryDecorator $registry, Cart $cart)
|
||||||
|
{
|
||||||
|
$this->oc = $registry;
|
||||||
|
$this->cart = $cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCart(): array
|
||||||
|
{
|
||||||
|
$this->oc->load->language('checkout/cart');
|
||||||
|
|
||||||
|
if ($this->oc->cart->hasProducts()) {
|
||||||
|
if (! $this->oc->cart->hasStock() && (! $this->oc->config->get('config_stock_checkout') || $this->oc->config->get(
|
||||||
|
'config_stock_warning'
|
||||||
|
))) {
|
||||||
|
$data['error_warning'] = $this->oc->language->get('error_stock');
|
||||||
|
} elseif (isset($this->oc->session->data['error'])) {
|
||||||
|
$data['error_warning'] = $this->oc->session->data['error'];
|
||||||
|
|
||||||
|
unset($this->oc->session->data['error']);
|
||||||
|
} else {
|
||||||
|
$data['error_warning'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->oc->config->get('config_customer_price') && ! $this->oc->customer->isLogged()) {
|
||||||
|
$data['attention'] = sprintf(
|
||||||
|
$this->oc->language->get('text_login'),
|
||||||
|
$this->oc->url->link('account/login'),
|
||||||
|
$this->oc->url->link('account/register')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data['attention'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->oc->session->data['success'])) {
|
||||||
|
$data['success'] = $this->oc->session->data['success'];
|
||||||
|
|
||||||
|
unset($this->oc->session->data['success']);
|
||||||
|
} else {
|
||||||
|
$data['success'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->oc->config->get('config_cart_weight')) {
|
||||||
|
$data['weight'] = $this->oc->weight->format(
|
||||||
|
$this->oc->cart->getWeight(),
|
||||||
|
$this->oc->config->get('config_weight_class_id'),
|
||||||
|
$this->oc->language->get('decimal_point'),
|
||||||
|
$this->oc->language->get('thousand_point')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$data['weight'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->oc->load->model('tool/image');
|
||||||
|
$this->oc->load->model('tool/upload');
|
||||||
|
|
||||||
|
$data['products'] = array();
|
||||||
|
|
||||||
|
$products = $this->oc->cart->getProducts();
|
||||||
|
|
||||||
|
foreach ($products as $product) {
|
||||||
|
$product_total = 0;
|
||||||
|
|
||||||
|
foreach ($products as $product_2) {
|
||||||
|
if ($product_2['product_id'] == $product['product_id']) {
|
||||||
|
$product_total += $product_2['quantity'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($product['minimum'] > $product_total) {
|
||||||
|
$data['error_warning'] = sprintf(
|
||||||
|
$this->oc->language->get('error_minimum'),
|
||||||
|
$product['name'],
|
||||||
|
$product['minimum']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($product['image']) {
|
||||||
|
$image = $this->oc->model_tool_image->resize(
|
||||||
|
$product['image'],
|
||||||
|
$this->oc->config->get('theme_' . $this->oc->config->get('config_theme') . '_image_cart_width'),
|
||||||
|
$this->oc->config->get('theme_' . $this->oc->config->get('config_theme') . '_image_cart_height')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$image = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$option_data = array();
|
||||||
|
|
||||||
|
foreach ($product['option'] as $option) {
|
||||||
|
if ($option['type'] != 'file') {
|
||||||
|
$value = $option['value'];
|
||||||
|
} else {
|
||||||
|
$upload_info = $this->oc->model_tool_upload->getUploadByCode($option['value']);
|
||||||
|
|
||||||
|
if ($upload_info) {
|
||||||
|
$value = $upload_info['name'];
|
||||||
|
} else {
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$option_data[] = array(
|
||||||
|
'name' => $option['name'],
|
||||||
|
'value' => (utf8_strlen($value) > 20 ? utf8_substr($value, 0, 20) . '..' : $value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$priceNumeric = 0;
|
||||||
|
$totalNumeric = 0;
|
||||||
|
|
||||||
|
// Display prices
|
||||||
|
if ($this->oc->customer->isLogged() || ! $this->oc->config->get('config_customer_price')) {
|
||||||
|
$unit_price = $this->oc->tax->calculate(
|
||||||
|
$product['price'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->oc->config->get('config_tax')
|
||||||
|
);
|
||||||
|
|
||||||
|
$priceNumeric = $unit_price;
|
||||||
|
$totalNumeric = $unit_price * $product['quantity'];
|
||||||
|
|
||||||
|
$price = $this->oc->currency->format($unit_price, $this->oc->session->data['currency']);
|
||||||
|
$total = $this->oc->currency->format($totalNumeric, $this->oc->session->data['currency']);
|
||||||
|
} else {
|
||||||
|
$price = false;
|
||||||
|
$total = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurring = '';
|
||||||
|
|
||||||
|
if ($product['recurring']) {
|
||||||
|
$frequencies = array(
|
||||||
|
'day' => $this->oc->language->get('text_day'),
|
||||||
|
'week' => $this->oc->language->get('text_week'),
|
||||||
|
'semi_month' => $this->oc->language->get('text_semi_month'),
|
||||||
|
'month' => $this->oc->language->get('text_month'),
|
||||||
|
'year' => $this->oc->language->get('text_year')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($product['recurring']['trial']) {
|
||||||
|
$recurring = sprintf(
|
||||||
|
$this->oc->language->get('text_trial_description'),
|
||||||
|
$this->oc->currency->format(
|
||||||
|
$this->oc->tax->calculate(
|
||||||
|
$product['recurring']['trial_price'] * $product['quantity'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->oc->config->get('config_tax')
|
||||||
|
),
|
||||||
|
$this->oc->session->data['currency']
|
||||||
|
),
|
||||||
|
$product['recurring']['trial_cycle'],
|
||||||
|
$frequencies[$product['recurring']['trial_frequency']],
|
||||||
|
$product['recurring']['trial_duration']
|
||||||
|
) . ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($product['recurring']['duration']) {
|
||||||
|
$recurring .= sprintf(
|
||||||
|
$this->oc->language->get('text_payment_description'),
|
||||||
|
$this->oc->currency->format(
|
||||||
|
$this->oc->tax->calculate(
|
||||||
|
$product['recurring']['price'] * $product['quantity'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->oc->config->get('config_tax')
|
||||||
|
),
|
||||||
|
$this->oc->session->data['currency']
|
||||||
|
),
|
||||||
|
$product['recurring']['cycle'],
|
||||||
|
$frequencies[$product['recurring']['frequency']],
|
||||||
|
$product['recurring']['duration']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$recurring .= sprintf(
|
||||||
|
$this->oc->language->get('text_payment_cancel'),
|
||||||
|
$this->oc->currency->format(
|
||||||
|
$this->oc->tax->calculate(
|
||||||
|
$product['recurring']['price'] * $product['quantity'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->oc->config->get('config_tax')
|
||||||
|
),
|
||||||
|
$this->oc->session->data['currency']
|
||||||
|
),
|
||||||
|
$product['recurring']['cycle'],
|
||||||
|
$frequencies[$product['recurring']['frequency']],
|
||||||
|
$product['recurring']['duration']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['products'][] = array(
|
||||||
|
'product_id' => (int) $product['product_id'],
|
||||||
|
'cart_id' => (int) $product['cart_id'],
|
||||||
|
'thumb' => $image,
|
||||||
|
'name' => $product['name'],
|
||||||
|
'model' => $product['model'],
|
||||||
|
'option' => $option_data,
|
||||||
|
'recurring' => $recurring,
|
||||||
|
'quantity' => (int) $product['quantity'],
|
||||||
|
'stock' => $product['stock'] ? true : ! (! $this->oc->config->get(
|
||||||
|
'config_stock_checkout'
|
||||||
|
) || $this->oc->config->get('config_stock_warning')),
|
||||||
|
'reward' => ($product['reward'] ? sprintf(
|
||||||
|
$this->oc->language->get('text_points'),
|
||||||
|
$product['reward']
|
||||||
|
) : ''),
|
||||||
|
'price' => $price,
|
||||||
|
'total' => $total,
|
||||||
|
'href' => $this->oc->url->link('product/product', 'product_id=' . $product['product_id']),
|
||||||
|
|
||||||
|
'price_numeric' => $priceNumeric,
|
||||||
|
'total_numeric' => $totalNumeric,
|
||||||
|
'reward_numeric' => $product['reward'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totals
|
||||||
|
$this->oc->load->model('setting/extension');
|
||||||
|
|
||||||
|
$totals = array();
|
||||||
|
$taxes = $this->oc->cart->getTaxes();
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
// Because __call can not keep var references so we put them into an array.
|
||||||
|
$total_data = array(
|
||||||
|
'totals' => &$totals,
|
||||||
|
'taxes' => &$taxes,
|
||||||
|
'total' => &$total
|
||||||
|
);
|
||||||
|
|
||||||
|
$sort_order = array();
|
||||||
|
|
||||||
|
$results = $this->oc->model_setting_extension->getExtensions('total');
|
||||||
|
|
||||||
|
foreach ($results as $key => $value) {
|
||||||
|
$sort_order[$key] = $this->oc->config->get('total_' . $value['code'] . '_sort_order');
|
||||||
|
}
|
||||||
|
|
||||||
|
array_multisort($sort_order, SORT_ASC, $results);
|
||||||
|
|
||||||
|
foreach ($results as $result) {
|
||||||
|
if ($this->oc->config->get('total_' . $result['code'] . '_status')) {
|
||||||
|
$this->oc->load->model('extension/total/' . $result['code']);
|
||||||
|
|
||||||
|
// We have to put the totals in an array so that they pass by reference.
|
||||||
|
$this->oc->{'model_extension_total_' . $result['code']}->getTotal($total_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sort_order = array();
|
||||||
|
|
||||||
|
foreach ($totals as $key => $value) {
|
||||||
|
$sort_order[$key] = $value['sort_order'];
|
||||||
|
}
|
||||||
|
|
||||||
|
array_multisort($sort_order, SORT_ASC, $totals);
|
||||||
|
|
||||||
|
$data['totals'] = array();
|
||||||
|
|
||||||
|
foreach ($totals as $total) {
|
||||||
|
$data['totals'][] = [
|
||||||
|
'code' => $total['code'],
|
||||||
|
'title' => $total['title'],
|
||||||
|
'value' => $total['value'],
|
||||||
|
'sort_order' => $total['sort_order'],
|
||||||
|
'text' => $this->oc->currency->format($total['value'], $this->oc->session->data['currency']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastTotal = $totals[count($totals) - 1] ?? false;
|
||||||
|
$data['total'] = $lastTotal ? $lastTotal['value'] : 0;
|
||||||
|
$data['total_products_count'] = $this->oc->cart->countProducts();
|
||||||
|
} else {
|
||||||
|
$data['text_error'] = $this->oc->language->get('text_empty');
|
||||||
|
$data['totals'] = [];
|
||||||
|
$data['total'] = 0;
|
||||||
|
$data['products'] = [];
|
||||||
|
$data['total_products_count'] = 0;
|
||||||
|
unset($this->oc->session->data['success']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$this->cart->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,16 @@
|
|||||||
use App\Handlers\CategoriesHandler;
|
use App\Handlers\CategoriesHandler;
|
||||||
use App\Handlers\CartHandler;
|
use App\Handlers\CartHandler;
|
||||||
use App\Handlers\HelloWorldHandler;
|
use App\Handlers\HelloWorldHandler;
|
||||||
use App\Handlers\OrderCreateHandler;
|
use App\Handlers\OrderHandler;
|
||||||
use App\Handlers\ProductsHandler;
|
use App\Handlers\ProductsHandler;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'products' => [ProductsHandler::class, 'handle'],
|
'products' => [ProductsHandler::class, 'handle'],
|
||||||
'product_show' => [ProductsHandler::class, 'show'],
|
'product_show' => [ProductsHandler::class, 'show'],
|
||||||
'order_create' => [OrderCreateHandler::class, 'handle'],
|
'storeOrder' => [OrderHandler::class, 'store'],
|
||||||
|
|
||||||
'categoriesList' => [CategoriesHandler::class, 'index'],
|
'categoriesList' => [CategoriesHandler::class, 'index'],
|
||||||
|
|
||||||
'checkout' => [CartHandler::class, 'checkout'],
|
'checkout' => [CartHandler::class, 'checkout'],
|
||||||
|
'getCart' => [CartHandler::class, 'index'],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container h-full">
|
||||||
<FullscreenViewport v-if="platform === 'ios' || platform === 'android'"/>
|
<FullscreenViewport v-if="platform === 'ios' || platform === 'android'"/>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<CartButton/>
|
<CartButton/>
|
||||||
|
|||||||
46
spa/src/components/Form/TgInput.vue
Normal file
46
spa/src/components/Form/TgInput.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<fieldset class="fieldset mb-0">
|
||||||
|
<input
|
||||||
|
:type="type"
|
||||||
|
:inputmode="inputMode"
|
||||||
|
class="input w-full"
|
||||||
|
:class="error ? 'input-error' : ''"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
v-model="model"
|
||||||
|
@input="$emit('clearError')"
|
||||||
|
/>
|
||||||
|
<p v-if="error" class="label">{{ error }}</p>
|
||||||
|
</fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
const model = defineModel();
|
||||||
|
const props = defineProps({
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['clearError']);
|
||||||
|
|
||||||
|
const inputMode = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'email': return 'email';
|
||||||
|
case 'tel': return 'tel';
|
||||||
|
case 'number': return 'numeric';
|
||||||
|
default: return 'text';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
30
spa/src/components/Form/TgTextarea.vue
Normal file
30
spa/src/components/Form/TgTextarea.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<fieldset class="fieldset mb-0">
|
||||||
|
<textarea
|
||||||
|
class="input w-full h-30"
|
||||||
|
:class="error ? 'input-error' : ''"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
v-model="model"
|
||||||
|
@input="$emit('clearError')"
|
||||||
|
rows="5"
|
||||||
|
/>
|
||||||
|
<p v-if="error" class="label">{{ error }}</p>
|
||||||
|
</fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const model = defineModel();
|
||||||
|
const props = defineProps({
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['clearError']);
|
||||||
|
</script>
|
||||||
@@ -19,7 +19,6 @@ const categoriesStore = useCategoriesStore();
|
|||||||
categoriesStore.fetchTopCategories();
|
categoriesStore.fetchTopCategories();
|
||||||
categoriesStore.fetchCategories();
|
categoriesStore.fetchCategories();
|
||||||
|
|
||||||
|
|
||||||
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
||||||
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import CategoriesList from "./views/CategoriesList.vue";
|
|||||||
import Cart from "./views/Cart.vue";
|
import Cart from "./views/Cart.vue";
|
||||||
import Products from "@/views/Products.vue";
|
import Products from "@/views/Products.vue";
|
||||||
import Checkout from "@/views/Checkout.vue";
|
import Checkout from "@/views/Checkout.vue";
|
||||||
|
import OrderCreated from "@/views/OrderCreated.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', name: 'home', component: Home},
|
{path: '/', name: 'home', component: Home},
|
||||||
@@ -14,6 +15,7 @@ const routes = [
|
|||||||
{path: '/category/:id', name: 'category.show', component: CategoriesList},
|
{path: '/category/:id', name: 'category.show', component: CategoriesList},
|
||||||
{path: '/cart', name: 'cart.show', component: Cart},
|
{path: '/cart', name: 'cart.show', component: Cart},
|
||||||
{path: '/checkout', name: 'checkout', component: Checkout},
|
{path: '/checkout', name: 'checkout', component: Checkout},
|
||||||
|
{path: '/success', name: 'order_created', component: OrderCreated},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {isNotEmpty} from "@/helpers.js";
|
import {isNotEmpty} from "@/helpers.js";
|
||||||
import {apiFetch} from "@/utils/ftch.js";
|
import {addToCart, cartEditItem, cartRemoveItem, getCart} from "@/utils/ftch.js";
|
||||||
|
|
||||||
export const useCartStore = defineStore('cart', {
|
export const useCartStore = defineStore('cart', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -26,7 +26,7 @@ export const useCartStore = defineStore('cart', {
|
|||||||
async getProducts() {
|
async getProducts() {
|
||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const data = await apiFetch('/index.php?route=extension/tgshop/handle/cart');
|
const {data} = await getCart();
|
||||||
this.items = data.products;
|
this.items = data.products;
|
||||||
this.productsCount = data.total_products_count;
|
this.productsCount = data.total_products_count;
|
||||||
this.totals = data.totals;
|
this.totals = data.totals;
|
||||||
@@ -62,10 +62,7 @@ export const useCartStore = defineStore('cart', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await apiFetch('/index.php?route=checkout/cart/add', {
|
const response = await addToCart(formData);
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(JSON.stringify(response.error));
|
throw new Error(JSON.stringify(response.error));
|
||||||
@@ -85,10 +82,7 @@ export const useCartStore = defineStore('cart', {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('key', rowId);
|
formData.append('key', rowId);
|
||||||
await apiFetch('/index.php?route=checkout/cart/remove', {
|
await cartRemoveItem(formData);
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
await this.getProducts();
|
await this.getProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -102,11 +96,7 @@ export const useCartStore = defineStore('cart', {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(`quantity[${cartId}]`, quantity);
|
formData.append(`quantity[${cartId}]`, quantity);
|
||||||
await apiFetch('/index.php?route=checkout/cart/edit', {
|
await cartEditItem(formData);
|
||||||
redirect: 'manual',
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
await this.getProducts();
|
await this.getProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
44
spa/src/stores/CheckoutStore.js
Normal file
44
spa/src/stores/CheckoutStore.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {isNotEmpty} from "@/helpers.js";
|
||||||
|
import {storeOrder} from "@/utils/ftch.js";
|
||||||
|
import {useCartStore} from "@/stores/CartStore.js";
|
||||||
|
|
||||||
|
export const useCheckoutStore = defineStore('checkout', {
|
||||||
|
state: () => ({
|
||||||
|
customer: {
|
||||||
|
firstName: "Иван",
|
||||||
|
lastName: "Васильевич",
|
||||||
|
email: "ival_vasil@mail.ru",
|
||||||
|
phone: "+79999999999",
|
||||||
|
address: "Москва, Красная площадь, 1",
|
||||||
|
comment: "Доставить срочно❗️",
|
||||||
|
},
|
||||||
|
|
||||||
|
validationErrors: {},
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
hasError: (state) => {
|
||||||
|
return (field) => isNotEmpty(state.validationErrors[field]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async makeOrder() {
|
||||||
|
await storeOrder(this.customer)
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response?.status === 422) {
|
||||||
|
this.validationErrors = error.response._data.data;
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await useCartStore().getProducts();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearError(field) {
|
||||||
|
this.validationErrors[field] = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -3,9 +3,13 @@
|
|||||||
themes: all;
|
themes: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, body, #app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
position: relative;
|
||||||
padding-top: var(--tg-content-safe-area-inset-top);
|
padding-top: var(--tg-content-safe-area-inset-top);
|
||||||
padding-bottom: var(--tg-content-safe-area-inset-bottom);
|
padding-bottom: var(--tg-content-safe-area-inset-bottom);
|
||||||
padding-left: var(--tg-content-safe-area-inset-left);
|
padding-left: var(--tg-content-safe-area-inset-left);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const apiFetch = ofetch.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async function (action, query = null, json = null) {
|
async function ftch(action, query = null, json = null) {
|
||||||
const options = {
|
const options = {
|
||||||
method: json ? 'POST' : 'GET',
|
method: json ? 'POST' : 'GET',
|
||||||
}
|
}
|
||||||
@@ -23,4 +23,39 @@ export default async function (action, query = null, json = null) {
|
|||||||
if (json) options.body = json;
|
if (json) options.body = json;
|
||||||
|
|
||||||
return await apiFetch(`${BASE_URL}index.php?route=extension/tgshop/handle&api_action=${action}`, options);
|
return await apiFetch(`${BASE_URL}index.php?route=extension/tgshop/handle&api_action=${action}`, options);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export async function storeOrder(data) {
|
||||||
|
return await apiFetch(`${BASE_URL}index.php?route=extension/tgshop/handle&api_action=storeOrder`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCart() {
|
||||||
|
return await ftch('getCart');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addToCart(data) {
|
||||||
|
return await apiFetch(`${BASE_URL}index.php?route=checkout/cart/add`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cartRemoveItem(data) {
|
||||||
|
return await apiFetch(`${BASE_URL}index.php?route=checkout/cart/remove`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cartEditItem(data) {
|
||||||
|
return await apiFetch(`${BASE_URL}index.php?route=checkout/cart/edit`, {
|
||||||
|
redirect: 'manual',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ftch;
|
||||||
|
|||||||
@@ -86,7 +86,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary" :disabled="cart.canCheckout === false">Перейти к оформлению</button>
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="cart.canCheckout === false"
|
||||||
|
@click="goToCheckout"
|
||||||
|
>Перейти к оформлению</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -108,8 +112,10 @@ import OptionRadio from "@/components/ProductOptions/Cart/OptionRadio.vue";
|
|||||||
import OptionCheckbox from "@/components/ProductOptions/Cart/OptionCheckbox.vue";
|
import OptionCheckbox from "@/components/ProductOptions/Cart/OptionCheckbox.vue";
|
||||||
import OptionText from "@/components/ProductOptions/Cart/OptionText.vue";
|
import OptionText from "@/components/ProductOptions/Cart/OptionText.vue";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
const cart = useCartStore();
|
const cart = useCartStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// const componentMap = {
|
// const componentMap = {
|
||||||
// radio: OptionRadio,
|
// radio: OptionRadio,
|
||||||
@@ -127,4 +133,8 @@ function removeItem(cartId) {
|
|||||||
cart.removeItem(cartId);
|
cart.removeItem(cartId);
|
||||||
window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
window.Telegram.WebApp.HapticFeedback.notificationOccurred('error');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToCheckout() {
|
||||||
|
router.push({name: 'checkout'});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,13 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="max-w-3xl mx-auto p-4 space-y-6 pb-30">
|
<div class="max-w-3xl mx-auto p-4 space-y-6 pb-30">
|
||||||
<h2 class="text-2xl">
|
<h2 class="text-2xl">
|
||||||
Корзина
|
Оформление заказа
|
||||||
<span class="loading loading-spinner loading-md"></span>
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<div class="card card-border bg-base-100 w-full">
|
||||||
|
<div class="card-body">
|
||||||
|
<TgInput
|
||||||
|
v-model="checkout.customer.firstName"
|
||||||
|
placeholder="Введите имя"
|
||||||
|
:error="checkout.validationErrors.firstName"
|
||||||
|
@clearError="checkout.clearError('firstName')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TgInput
|
||||||
|
v-model="checkout.customer.lastName"
|
||||||
|
placeholder="Введите фамилию"
|
||||||
|
:error="checkout.validationErrors.lastName"
|
||||||
|
@clearError="checkout.clearError('lastName')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TgInput
|
||||||
|
v-model="checkout.customer.email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Введите email"
|
||||||
|
:error="checkout.validationErrors.email"
|
||||||
|
@clearError="checkout.clearError('email')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TgInput
|
||||||
|
v-model="checkout.customer.phone"
|
||||||
|
type="tel"
|
||||||
|
placeholder="Введите телефон"
|
||||||
|
:error="checkout.validationErrors.phone"
|
||||||
|
@clearError="checkout.clearError('phone')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TgInput
|
||||||
|
v-model="checkout.customer.address"
|
||||||
|
placeholder="Адрес доставки"
|
||||||
|
:error="checkout.validationErrors.address"
|
||||||
|
@clearError="checkout.clearError('address')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TgTextarea
|
||||||
|
v-model="checkout.customer.comment"
|
||||||
|
placeholder="Комментарий"
|
||||||
|
:error="checkout.validationErrors.comment"
|
||||||
|
@clearError="checkout.clearError('comment')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="fixed px-4 pb-10 pt-4 bottom-0 left-0 w-full bg-base-200 z-50 flex justify-between items-center gap-2 border-t-1 border-t-base-300">
|
||||||
|
<button class="btn btn-primary w-full" @click="onCreateBtnClick">Создать заказ</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {useCheckoutStore} from "@/stores/CheckoutStore.js";
|
||||||
|
import TgInput from "@/components/Form/TgInput.vue";
|
||||||
|
import TgTextarea from "@/components/Form/TgTextarea.vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const checkout = useCheckoutStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function onCreateBtnClick() {
|
||||||
|
await checkout.makeOrder();
|
||||||
|
router.push({name: 'order_created'});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
16
spa/src/views/OrderCreated.vue
Normal file
16
spa/src/views/OrderCreated.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="max-w-3xl mx-auto p-4 space-y-6 pb-30 flex flex-col items-center h-full justify-center">
|
||||||
|
<div class="flex flex-col justify-center items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-20 text-success">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 0 1-1.043 3.296 3.745 3.745 0 0 1-3.296 1.043A3.745 3.745 0 0 1 12 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 0 1-3.296-1.043 3.745 3.745 0 0 1-1.043-3.296A3.745 3.745 0 0 1 3 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 0 1 1.043-3.296 3.746 3.746 0 0 1 3.296-1.043A3.746 3.746 0 0 1 12 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 0 1 3.296 1.043 3.746 3.746 0 0 1 1.043 3.296A3.745 3.745 0 0 1 21 12Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="text-lg mb-3">Ваш заказ создан!</p>
|
||||||
|
<RouterLink class="btn btn-primary" to="/">На главную</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user