diff --git a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
index b81e5d5..1676847 100755
--- a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
+++ b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
@@ -547,17 +547,18 @@ HTML,
],
'orders' => [
- 'module_tgshop_order_customer_group_id' => [
- 'type' => 'select',
- 'options' => $this->getCustomerGroups(),
- 'help' => 'Группа покупателей, которая будет назначена для заказов, оформленных через Telegram-магазин.',
- ],
-
'module_tgshop_order_default_status_id' => [
'type' => 'select',
'options' => $this->getOrderStatuses(),
'help' => 'Статус, с которым будут создаваться заказы через Telegram по умолчанию.',
],
+
+ 'module_tgshop_order_customer_group_id' => [
+ 'hidden' => true,
+ 'type' => 'select',
+ 'options' => $this->getCustomerGroups(),
+ 'help' => 'Группа покупателей, которая будет назначена для заказов, оформленных через Telegram-магазин.',
+ ],
],
];
}
diff --git a/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php b/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php
index 3a64c76..9d2717b 100755
--- a/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php
+++ b/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php
@@ -45,8 +45,7 @@ class ControllerExtensionTgshopHandle extends Controller
'app_debug' => $appDebug,
'oc_config_tax' => $this->config->get('config_tax'),
'oc_default_currency' => $this->config->get('config_currency'),
- // ID группы покупателей, которая будет использоаваться в заказах через Телеграм.
- 'oc_customer_group_id' => $this->config->get('module_tgshop_order_customer_group_id'),
+ 'oc_customer_group_id' => $this->config->get('config_customer_group_id'),
// ID магазина, для которого будут создаваться заказы из Телеграм
'oc_store_id' => 0,
// Название магазина, для которого будут создаваться заказы из Телеграм
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/OrderCreateService.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/OrderCreateService.php
index 13c9b2d..512ea12 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/OrderCreateService.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/OrderCreateService.php
@@ -177,6 +177,7 @@ class OrderCreateService
$this->cartService->flush();
$orderData['order_id'] = $orderId;
+ $orderData['total_numeric'] = $orderData['total'] ?? 0;
$orderData['total'] = $cart['total_text'] ?? '';
$this->sendNotifications($orderData, $data['tgData']);
@@ -192,6 +193,8 @@ class OrderCreateService
'id' => $orderData['order_id'],
'created_at' => $dateTimeFormatted,
'total' => $orderData['total'],
+ 'final_total_numeric' => $orderData['total_numeric'],
+ 'currency' => $currencyCode,
];
}
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php
index f38ef5c..fcfce52 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php
@@ -214,6 +214,7 @@ class ProductsService
$configTax = $this->oc->config->get('config_tax');
$product_info = $this->oc->model_catalog_product->getProduct($productId);
+ $currency = $this->oc->session->data['currency'];
if (! $product_info) {
throw new EntityNotFoundException('Product with id ' . $productId . ' not found');
@@ -280,24 +281,24 @@ class ProductsService
$data['images'] = $images;
- $data['price'] = $this->currency->format(
- $this->tax->calculate(
- $product_info['price'],
- $product_info['tax_class_id'],
- $configTax,
- ),
- $this->oc->session->data['currency']
+ $productPrice = $this->tax->calculate(
+ $product_info['price'],
+ $product_info['tax_class_id'],
+ $configTax,
);
+ $data['price'] = $this->currency->format($productPrice, $currency);
+ $data['currency'] = $currency;
+ $data['final_price_numeric'] = $productPrice;
if (! is_null($product_info['special']) && (float) $product_info['special'] >= 0) {
- $data['special'] = $this->currency->format(
- $this->tax->calculate(
- $product_info['special'],
- $product_info['tax_class_id'],
- $configTax,
- ),
- $this->oc->session->data['currency']
+ $productSpecialPrice = $this->tax->calculate(
+ $product_info['special'],
+ $product_info['tax_class_id'],
+ $configTax,
);
+
+ $data['special'] = $this->currency->format($productSpecialPrice, $currency);
+ $data['final_price_numeric'] = $productSpecialPrice;
$tax_price = (float) $product_info['special'];
} else {
$data['special'] = false;
@@ -305,7 +306,7 @@ class ProductsService
}
if ($configTax) {
- $data['tax'] = $this->currency->format($tax_price, $this->oc->session->data['currency']);
+ $data['tax'] = $this->currency->format($tax_price, $currency);
} else {
$data['tax'] = false;
}
@@ -323,7 +324,7 @@ class ProductsService
$product_info['tax_class_id'],
$configTax,
),
- $this->oc->session->data['currency']
+ $currency
)
);
}
@@ -341,7 +342,7 @@ class ProductsService
$product_info['tax_class_id'],
$configTax ? 'P' : false
),
- $this->oc->session->data['currency']
+ $currency
);
$product_option_value_data[] = array(
diff --git a/spa/src/constants/yaMetrikaGoals.js b/spa/src/constants/yaMetrikaGoals.js
new file mode 100644
index 0000000..cdc557e
--- /dev/null
+++ b/spa/src/constants/yaMetrikaGoals.js
@@ -0,0 +1,14 @@
+export const YA_METRIKA_GOAL = {
+ ADD_TO_CART: 'add_to_cart',
+ CREATE_ORDER: 'create_order',
+ ORDER_CREATED_SUCCESS: 'order_created_success',
+ VIEW_PRODUCT: 'view_product',
+ VIEW_CART: 'view_cart',
+ VIEW_CHECKOUT: 'view_checkout',
+ VIEW_HOME: 'view_home',
+ VIEW_FILTERS: 'view_filters',
+ FILTERS_APPLY: 'filters_apply',
+ FILTERS_RESET: 'filters_reset',
+ VIEW_SEARCH: 'view_search',
+ PERFORM_SEARCH: 'perform_search',
+};
diff --git a/spa/src/router.js b/spa/src/router.js
index 2ef525c..779ba49 100644
--- a/spa/src/router.js
+++ b/spa/src/router.js
@@ -35,3 +35,9 @@ export const router = createRouter({
history: createWebHashHistory('/image/catalog/tgshopspa/'),
routes,
});
+
+router.beforeEach((to, from, next) => {
+ const ym = useYaMetrikaStore();
+ ym.prevPath = from.path;
+ next();
+});
diff --git a/spa/src/stores/CategoriesStore.js b/spa/src/stores/CategoriesStore.js
index 3c7ed72..288b31c 100644
--- a/spa/src/stores/CategoriesStore.js
+++ b/spa/src/stores/CategoriesStore.js
@@ -38,5 +38,24 @@ export const useCategoriesStore = defineStore('categories', {
this.isLoading = false;
}
},
+
+ async findCategoryById(id, list = []) {
+ if (! id) return null;
+
+ if (list && list.length === 0) {
+ await this.fetchCategories();
+ list = this.categories;
+ }
+
+ for (const cat of list) {
+ if (parseInt(cat.id) === parseInt(id)) return cat;
+ if (cat.children?.length) {
+ const found = await this.findCategoryById(id, cat.children);
+ if (found) return found;
+ }
+ }
+
+ return null;
+ }
},
});
diff --git a/spa/src/stores/SearchStore.js b/spa/src/stores/SearchStore.js
index eb0973f..68aa0c3 100644
--- a/spa/src/stores/SearchStore.js
+++ b/spa/src/stores/SearchStore.js
@@ -1,5 +1,7 @@
import {defineStore} from "pinia";
import ftch from "@/utils/ftch.js";
+import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
+import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
export const useSearchStore = defineStore('search', {
state: () => ({
@@ -31,6 +33,10 @@ export const useSearchStore = defineStore('search', {
return this.reset();
}
+ useYaMetrikaStore().reachGoal(YA_METRIKA_GOAL.PERFORM_SEARCH, {
+ keyword: this.search,
+ });
+
try {
this.isLoading = true;
this.products = await ftch('products', {
diff --git a/spa/src/stores/yaMetrikaStore.js b/spa/src/stores/yaMetrikaStore.js
index 5df1ebc..5fe2074 100644
--- a/spa/src/stores/yaMetrikaStore.js
+++ b/spa/src/stores/yaMetrikaStore.js
@@ -1,9 +1,12 @@
import {defineStore} from "pinia";
import {useSettingsStore} from "@/stores/SettingsStore.js";
+import sha256 from 'crypto-js/sha256';
+import {toRaw} from "vue";
export const useYaMetrikaStore = defineStore('ya_metrika', {
state: () => ({
queue: [],
+ prevPath: null,
}),
actions: {
@@ -16,6 +19,8 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
const fullUrl = `/#${url}`;
+ params.referer = params.referer ?? this.prevPath;
+
if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
console.debug('[ym] Hit ', fullUrl);
console.debug('[ym] ID ', window.YA_METRIKA_ID);
@@ -33,6 +38,49 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
}
},
+ reachGoal(target, params = {}) {
+ const settings = useSettingsStore();
+ if (!settings.ya_metrika_enabled) {
+ console.debug('[ym] Yandex Metrika disabled in settings.');
+ return;
+ }
+
+ if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
+ console.debug('[ym] reachGoal ', target, ' params: ', params);
+ window.ym(window.YA_METRIKA_ID, 'reachGoal', target, params);
+ } else {
+ console.debug('[ym] Yandex Metrika is not initialized. Pushed to queue.');
+ this.queue.push({
+ event: 'reachGoal',
+ payload: {
+ target,
+ params
+ },
+ });
+ }
+ },
+
+ initUserParams() {
+ if (typeof window.ym === 'function' && window.YA_METRIKA_ID !== undefined) {
+ let tgID = null;
+
+ if (window?.Telegram?.WebApp?.initDataUnsafe?.user?.id) {
+ tgID = sha256(window.Telegram.WebApp.initDataUnsafe.user.id).toString();
+ }
+
+ const userParams = {
+ tg_id: tgID,
+ language: window.Telegram?.WebApp?.initDataUnsafe?.user?.language_code || 'unknown',
+ platform: window.Telegram?.WebApp?.platform || 'unknown',
+ };
+
+ window.ym(window.YA_METRIKA_ID, 'userParams', userParams);
+ console.debug('[ym] User params initialized: ', userParams);
+ } else {
+ console.debug('[ym] Yandex Metrika is not initialized. Could not init user params.');
+ }
+ },
+
processQueue() {
if (this.queue.length === 0) {
return;
@@ -43,8 +91,10 @@ export const useYaMetrikaStore = defineStore('ya_metrika', {
while (this.queue.length > 0) {
const item = this.queue.shift();
if (item.event === 'hit') {
- console.debug('[ym] Queue ', item);
+ console.debug('[ym] Queue ', toRaw(item));
window.ym(window.YA_METRIKA_ID, item.event, item.payload.url, item.payload.params);
+ } else if (item.event === 'reachGoal') {
+ window.ym(window.YA_METRIKA_ID, item.event, item.payload.target, item.payload.params);
} else {
console.error('[ym] Unsupported queue event: ', item.event);
}
diff --git a/spa/src/utils/yaMetrika.js b/spa/src/utils/yaMetrika.js
index 3a516ee..ca9cb80 100644
--- a/spa/src/utils/yaMetrika.js
+++ b/spa/src/utils/yaMetrika.js
@@ -23,6 +23,7 @@ export function injectYaMetrika() {
window.YA_METRIKA_ID = getMetrikaId();
console.debug('[Init] Detected Yandex.Metrika ID:', window.YA_METRIKA_ID);
const yaMetrika = useYaMetrikaStore();
+ yaMetrika.initUserParams();
yaMetrika.processQueue();
}
}
diff --git a/spa/src/views/Cart.vue b/spa/src/views/Cart.vue
index 621fe2e..7b07744 100644
--- a/spa/src/views/Cart.vue
+++ b/spa/src/views/Cart.vue
@@ -139,9 +139,13 @@ import OptionRadio from "@/components/ProductOptions/Cart/OptionRadio.vue";
import OptionCheckbox from "@/components/ProductOptions/Cart/OptionCheckbox.vue";
import OptionText from "@/components/ProductOptions/Cart/OptionText.vue";
import {computed, onMounted} from "vue";
-import {useRouter} from "vue-router";
+import {useRoute, useRouter} from "vue-router";
import {useSettingsStore} from "@/stores/SettingsStore.js";
+import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
+import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
+const route = useRoute();
+const yaMetrika = useYaMetrikaStore();
const cart = useCartStore();
const router = useRouter();
const settings = useSettingsStore();
@@ -169,11 +173,15 @@ function goToCheckout() {
onMounted(async () => {
window.document.title = 'Корзина покупок';
+ yaMetrika.pushHit(route.path, {
+ title: 'Корзина покупок',
+ });
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_CART);
});
\ No newline at end of file
diff --git a/spa/src/views/CategoriesList.vue b/spa/src/views/CategoriesList.vue
index 759e0f7..a359897 100644
--- a/spa/src/views/CategoriesList.vue
+++ b/spa/src/views/CategoriesList.vue
@@ -58,9 +58,11 @@ import {router} from "@/router.js";
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
import {useRoute} from "vue-router";
import CategoryItem from "@/components/CategoriesList/CategoryItem.vue";
+import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
const route = useRoute();
const categoriesStore = useCategoriesStore();
+const yaMetrika = useYaMetrikaStore();
const parentId = computed(() => route.params.id ? Number(route.params.id) : null);
@@ -106,6 +108,9 @@ function showProductsInParentCategory() {
onMounted(async () => {
window.document.title = 'Каталог';
+ yaMetrika.pushHit(route.path, {
+ title: 'Каталог',
+ });
await categoriesStore.fetchCategories();
});
diff --git a/spa/src/views/Checkout.vue b/spa/src/views/Checkout.vue
index 90cc494..1220559 100644
--- a/spa/src/views/Checkout.vue
+++ b/spa/src/views/Checkout.vue
@@ -69,11 +69,15 @@
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";
+import {useRoute, useRouter} from "vue-router";
import {computed, onMounted, ref} from "vue";
import {IMaskComponent} from "vue-imask";
+import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
+import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
const checkout = useCheckoutStore();
+const yaMetrika = useYaMetrikaStore();
+const route = useRoute();
const router = useRouter();
const error = ref(null);
@@ -84,7 +88,15 @@ const btnText = computed(() => {
async function onCreateBtnClick() {
try {
error.value = null;
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.CREATE_ORDER, {
+ price: checkout.order?.final_total_numeric,
+ currency: checkout.order?.currency,
+ });
await checkout.makeOrder();
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.ORDER_CREATED_SUCCESS, {
+ price: checkout.order?.final_total_numeric,
+ currency: checkout.order?.currency,
+ });
router.push({name: 'order_created'});
} catch {
error.value = 'Невозможно создать заказ.';
@@ -93,5 +105,9 @@ async function onCreateBtnClick() {
onMounted(async () => {
window.document.title = 'Оформление заказа';
+ yaMetrika.pushHit(route.path, {
+ title: 'Оформление заказа',
+ });
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_CHECKOUT);
});
diff --git a/spa/src/views/Filters.vue b/spa/src/views/Filters.vue
index 4233228..2487fc8 100644
--- a/spa/src/views/Filters.vue
+++ b/spa/src/views/Filters.vue
@@ -49,8 +49,10 @@ import {nextTick, onMounted} from "vue";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import ProductPrice from "@/components/ProductFilters/Components/ProductPrice.vue";
import ForMainPage from "@/components/ProductFilters/Components/ForMainPage.vue";
-import {useRouter} from "vue-router";
+import {useRoute, useRouter} from "vue-router";
import ProductCategory from "@/components/ProductFilters/Components/ProductCategory/ProductCategory.vue";
+import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
+import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
defineOptions({
name: 'Filters'
@@ -63,6 +65,8 @@ const componentMap = {
};
const router = useRouter();
+const yaMetrika = useYaMetrikaStore();
+const route = useRoute();
const emit = defineEmits(['close', 'apply']);
const filtersStore = useProductFiltersStore();
@@ -72,6 +76,7 @@ const applyFilters = async () => {
filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft));
console.debug('Filters: apply filters. Hash for router: ', filtersStore.paramsHashForRouter);
haptic.impactOccurred('soft');
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.FILTERS_APPLY);
await nextTick();
router.back();
}
@@ -80,6 +85,7 @@ const resetFilters = async () => {
filtersStore.applied = filtersStore.default;
console.debug('Filters: reset filters. Hash for router: ', filtersStore.paramsHashForRouter);
haptic.notificationOccurred('success');
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.FILTERS_RESET);
await nextTick();
window.scrollTo(0, 0);
router.back();
@@ -89,6 +95,9 @@ onMounted(async () => {
console.debug('Filters: OnMounted');
window.document.title = 'Фильтры';
+ yaMetrika.pushHit(route.path, {title: 'Фильтры'});
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_FILTERS);
+
if (filtersStore.applied?.rules) {
console.debug('Filters: Found applied filters.');
filtersStore.draft = JSON.parse(JSON.stringify(filtersStore.applied));
diff --git a/spa/src/views/Home.vue b/spa/src/views/Home.vue
index fef42f9..7a2a8c2 100644
--- a/spa/src/views/Home.vue
+++ b/spa/src/views/Home.vue
@@ -38,6 +38,7 @@ import ftch from "@/utils/ftch.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import Banner from "@/components/Banner.vue";
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
+import {YA_METRIKA_GOAL} from "@/constants/yaMetrikaGoals.js";
defineOptions({
name: 'Home'
@@ -99,7 +100,10 @@ async function onLoadMore() {
}
onActivated(() => {
- yaMetrika.pushHit('/');
+ yaMetrika.pushHit('/', {
+ title: 'Главная страница',
+ });
+ yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_HOME);
});
onMounted(async () => {
diff --git a/spa/src/views/OrderCreated.vue b/spa/src/views/OrderCreated.vue
index 0d32781..34682f5 100644
--- a/spa/src/views/OrderCreated.vue
+++ b/spa/src/views/OrderCreated.vue
@@ -39,10 +39,17 @@
diff --git a/spa/src/views/Product.vue b/spa/src/views/Product.vue
index eb964cb..4ebbd8b 100644
--- a/spa/src/views/Product.vue
+++ b/spa/src/views/Product.vue
@@ -106,7 +106,7 @@
class="btn btn-primary btn-lg w-full"
:class="isInCart ? 'btn-success' : 'btn-primary'"
:disabled="cart.isLoading || canAddToCart === false"
- @click="actionBtnClick"
+ @click="onCartBtnClick"
>
{{ btnText }}
@@ -156,7 +156,7 @@