@@ -132,25 +150,35 @@
—
+
+
+ {{ formatDate(data.privacy_consented_at) }}
+
+ —
+
{{ formatDate(data.created_at) }}
-
+
-
+
-
+
-
+
-
+
ID: {{ selectedCustomer.telegram_user_id }}
-
+
Пользователь не разрешил писать ему в PM
@@ -245,8 +274,8 @@
diff --git a/frontend/spa/src/main.js b/frontend/spa/src/main.js
index f7aa5d7..ee31107 100644
--- a/frontend/spa/src/main.js
+++ b/frontend/spa/src/main.js
@@ -8,7 +8,7 @@ import {useSettingsStore} from "@/stores/SettingsStore.js";
import ApplicationError from "@/ApplicationError.vue";
import AppMetaInitializer from "@/utils/AppMetaInitializer.ts";
import {injectYaMetrika} from "@/utils/yaMetrika.js";
-import {saveTelegramCustomer} from "@/utils/ftch.js";
+import {checkIsUserPrivacyConsented, saveTelegramCustomer} from "@/utils/ftch.js";
import {register} from 'swiper/element/bundle';
import 'swiper/element/bundle';
@@ -59,6 +59,22 @@ settings.load()
}
}
})
+ .then(() => {
+ (async () => {
+ try {
+ const response = await checkIsUserPrivacyConsented();
+ settings.is_privacy_consented = response?.data?.is_privacy_consented;
+ if (settings.is_privacy_consented) {
+ console.info('[Init] Privacy Policy consent given by user.');
+ } else {
+ console.info('[Init] Privacy Policy consent NOT given by user.');
+ }
+ } catch (error) {
+ console.error('[Init] Failed to check Telegram user consent.');
+ settings.is_privacy_consented = false;
+ }
+ })();
+ })
.then(() => blocks.processBlocks(settings.mainpage_blocks))
.then(async () => {
console.debug('Load default filters for the main page');
diff --git a/frontend/spa/src/stores/SettingsStore.js b/frontend/spa/src/stores/SettingsStore.js
index 6746613..05b1aef 100644
--- a/frontend/spa/src/stores/SettingsStore.js
+++ b/frontend/spa/src/stores/SettingsStore.js
@@ -29,6 +29,8 @@ export const useSettingsStore = defineStore('settings', {
text_order_created_success: 'Заказ успешно оформлен.',
},
mainpage_blocks: [],
+ is_privacy_consented: false,
+ privacy_policy_link: false,
}),
actions: {
@@ -53,6 +55,7 @@ export const useSettingsStore = defineStore('settings', {
this.currency_code = settings.currency_code;
this.texts = settings.texts;
this.mainpage_blocks = settings.mainpage_blocks;
+ this.privacy_policy_link = settings.privacy_policy_link;
}
}
});
diff --git a/frontend/spa/src/utils/ftch.js b/frontend/spa/src/utils/ftch.js
index c036c21..426cc1c 100644
--- a/frontend/spa/src/utils/ftch.js
+++ b/frontend/spa/src/utils/ftch.js
@@ -107,4 +107,12 @@ export async function saveTelegramCustomer(userData) {
});
}
+export async function checkIsUserPrivacyConsented() {
+ return await ftch('checkIsUserPrivacyConsented');
+}
+
+export async function userPrivacyConsent() {
+ return await ftch('userPrivacyConsent');
+}
+
export default ftch;
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramCustomersHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramCustomersHandler.php
index bca93da..b6e3c1e 100644
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramCustomersHandler.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/TelegramCustomersHandler.php
@@ -88,6 +88,7 @@ class TelegramCustomersHandler
'photo_url',
'last_seen_at',
'referral',
+ 'privacy_consented_at',
'created_at',
'updated_at',
])
@@ -322,6 +323,7 @@ class TelegramCustomersHandler
'photo_url' => $customer['photo_url'],
'last_seen_at' => $customer['last_seen_at'],
'referral' => $customer['referral'],
+ 'privacy_consented_at' => $customer['privacy_consented_at'],
'created_at' => $customer['created_at'],
'updated_at' => $customer['updated_at'],
];
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations/20260101000006_add_privacy_consented_at_to_telecart_customers_table.php b/module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations/20260101000006_add_privacy_consented_at_to_telecart_customers_table.php
new file mode 100644
index 0000000..b3abdb2
--- /dev/null
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations/20260101000006_add_privacy_consented_at_to_telecart_customers_table.php
@@ -0,0 +1,16 @@
+database->statement($sql);
+ }
+};
+
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php
index 5b55c2f..49ae3ef 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramService.php
@@ -7,15 +7,24 @@ use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\Enums\ChatAction;
+use Openguru\OpenCartFramework\Telegram\Exceptions\DecodeTelegramInitDataException;
use Openguru\OpenCartFramework\Telegram\Exceptions\TelegramClientException;
+use Psr\Log\LoggerInterface;
class TelegramService
{
private ?string $botToken;
+ private TelegramInitDataDecoder $initDataDecoder;
+ private LoggerInterface $logger;
- public function __construct(?string $botToken = null)
- {
+ public function __construct(
+ TelegramInitDataDecoder $initDataDecoder,
+ LoggerInterface $logger,
+ ?string $botToken = null
+ ) {
$this->botToken = $botToken;
+ $this->initDataDecoder = $initDataDecoder;
+ $this->logger = $logger;
}
public function escapeTelegramMarkdownV2(string $text): string
@@ -194,4 +203,21 @@ class TelegramService
return $response['result'];
}
+
+ public function userId(?string $initDataRaw = null): ?int
+ {
+ if (! $initDataRaw) {
+ return null;
+ }
+
+ try {
+ $decoded = $this->initDataDecoder->decode($initDataRaw);
+
+ return Arr::get($decoded, 'user.id');
+ } catch (DecodeTelegramInitDataException $e) {
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
+ }
+
+ return null;
+ }
}
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramServiceProvider.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramServiceProvider.php
index c4d32a7..bc005c3 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramServiceProvider.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Telegram/TelegramServiceProvider.php
@@ -14,7 +14,11 @@ class TelegramServiceProvider extends ServiceProvider
$this->container->singleton(TelegramService::class, function (Application $app) {
$botToken = $app->getConfigValue('telegram.bot_token');
- return new TelegramService($botToken);
+ return new TelegramService(
+ $app->get(TelegramInitDataDecoder::class),
+ $app->get(LoggerInterface::class),
+ $botToken,
+ );
});
$this->container->singleton(SignatureValidator::class, function (Application $app) {
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/PrivacyPolicyHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/PrivacyPolicyHandler.php
new file mode 100644
index 0000000..27e658c
--- /dev/null
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/PrivacyPolicyHandler.php
@@ -0,0 +1,73 @@
+telegramService = $telegramService;
+ $this->telegramCustomer = $telegramCustomer;
+ $this->logger = $logger;
+ }
+
+ public function checkIsUserPrivacyConsented(Request $request): JsonResponse
+ {
+ $isPrivacyConsented = false;
+ $telegramUserId = $this->telegramService->userId($request->header(TelegramHeader::INIT_DATA));
+
+ if (! $telegramUserId) {
+ return new JsonResponse([
+ 'data' => [
+ 'is_privacy_consented' => false,
+ ],
+ ]);
+ }
+
+ $customer = $this->telegramCustomer->findByTelegramUserId($telegramUserId);
+
+ if ($customer) {
+ $isPrivacyConsented = Arr::get($customer, 'privacy_consented_at') !== null;
+ }
+
+ return new JsonResponse([
+ 'data' => [
+ 'is_privacy_consented' => $isPrivacyConsented,
+ ],
+ ]);
+ }
+
+ public function userPrivacyConsent(Request $request): JsonResponse
+ {
+ $telegramUserId = $this->telegramService->userId($request->header(TelegramHeader::INIT_DATA));
+
+ if ($telegramUserId) {
+ $this->telegramCustomer->updateByTelegramUserId($telegramUserId, [
+ 'privacy_consented_at' => Carbon::now()->toDateTimeString(),
+ ]);
+ } else {
+ $this->logger->warning(
+ 'Could not find customer with telegram user_id: ' . $telegramUserId . ' to give privacy consent.'
+ );
+ }
+
+ return new JsonResponse([], Response::HTTP_NO_CONTENT);
+ }
+}
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php
index 167351a..3d4e68d 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php
@@ -65,6 +65,7 @@ class SettingsHandler
'currency_code' => $this->settings->config()->getStore()->getOcDefaultCurrency(),
'texts' => $this->settings->config()->getTexts()->toArray(),
'mainpage_blocks' => $this->settings->get('mainpage_blocks', []),
+ 'privacy_policy_link' => $this->settings->get('app.privacy_policy_link'),
]);
}
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php
index aa5dfc2..3a8e72f 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php
@@ -7,13 +7,15 @@ use App\Handlers\FiltersHandler;
use App\Handlers\FormsHandler;
use App\Handlers\HealthCheckHandler;
use App\Handlers\OrderHandler;
+use App\Handlers\PrivacyPolicyHandler;
use App\Handlers\ProductsHandler;
use App\Handlers\SettingsHandler;
-use App\Handlers\TelegramHandler;
use App\Handlers\TelegramCustomerHandler;
+use App\Handlers\TelegramHandler;
return [
'categoriesList' => [CategoriesHandler::class, 'index'],
+ 'checkIsUserPrivacyConsented' => [PrivacyPolicyHandler::class, 'checkIsUserPrivacyConsented'],
'checkout' => [CartHandler::class, 'checkout'],
'filtersForMainPage' => [FiltersHandler::class, 'getFiltersForMainPage'],
'getCart' => [CartHandler::class, 'index'],
@@ -27,5 +29,6 @@ return [
'settings' => [SettingsHandler::class, 'index'],
'storeOrder' => [OrderHandler::class, 'store'],
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
+ 'userPrivacyConsent' => [PrivacyPolicyHandler::class, 'userPrivacyConsent'],
'webhook' => [TelegramHandler::class, 'webhook'],
];
diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Telegram/TelegramServiceTest.php b/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Telegram/TelegramServiceTest.php
index 1497823..d7ca584 100755
--- a/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Telegram/TelegramServiceTest.php
+++ b/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Telegram/TelegramServiceTest.php
@@ -2,6 +2,7 @@
namespace Telegram;
+use Openguru\OpenCartFramework\Telegram\TelegramInitDataDecoder;
use Openguru\OpenCartFramework\Telegram\TelegramService;
use Tests\TestCase;
@@ -12,7 +13,10 @@ class TelegramServiceTest extends TestCase
protected function setUp(): void
{
parent::setUp();
- $this->service = new TelegramService();
+ $this->service = new TelegramService(
+ new TelegramInitDataDecoder(),
+ $this->getNullLogger(),
+ );
}
public function testDoesNotEscapeNormalCharacters(): void