feat: track and push TeleCart Pulse events

This commit is contained in:
2025-11-30 16:52:32 +03:00
parent fc8044484e
commit ef785654b9
19 changed files with 583 additions and 70 deletions

View File

@@ -79,6 +79,7 @@ class TelegramCustomersHandler
'id',
'telegram_user_id',
'oc_customer_id',
'tracking_id',
'username',
'first_name',
'last_name',
@@ -323,6 +324,7 @@ class TelegramCustomersHandler
'id' => (int) $customer['id'],
'telegram_user_id' => (int) $customer['telegram_user_id'],
'oc_customer_id' => (int) $customer['oc_customer_id'],
'tracking_id' => $customer['tracking_id'],
'username' => $customer['username'],
'first_name' => $customer['first_name'],
'last_name' => $customer['last_name'],

View File

@@ -30,7 +30,8 @@
"psr/container": "^2.0",
"psr/log": "^1.1",
"symfony/cache": "^5.4",
"vlucas/phpdotenv": "^5.6"
"vlucas/phpdotenv": "^5.6",
"ramsey/uuid": "^4.2"
},
"require-dev": {
"doctrine/sql-formatter": "^1.3",

View File

@@ -4,8 +4,68 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0c1bcdf986f5b31fb943e21467785c64",
"content-hash": "049ebb1f7c985aa2bbbe3578c203fb37",
"packages": [
{
"name": "brick/math",
"version": "0.9.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
"reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
"vimeo/psalm": "4.9.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Brick\\Math\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Arbitrary-precision arithmetic library",
"keywords": [
"Arbitrary-precision",
"BigInteger",
"BigRational",
"arithmetic",
"bigdecimal",
"bignum",
"brick",
"math"
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.9.3"
},
"funding": [
{
"url": "https://github.com/BenMorel",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/brick/math",
"type": "tidelift"
}
],
"time": "2021-08-15T20:50:18+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
"version": "2.1.0",
@@ -1488,6 +1548,194 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "ramsey/collection",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4",
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"symfony/polyfill-php81": "^1.23"
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Ramsey",
"email": "ben@benramsey.com",
"homepage": "https://benramsey.com"
}
],
"description": "A PHP library for representing and manipulating collections.",
"keywords": [
"array",
"collection",
"hash",
"map",
"queue",
"set"
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/1.3.0"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2022-12-27T19:12:24+00:00"
},
{
"name": "ramsey/uuid",
"version": "4.2.3",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
"reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
"shasum": ""
},
"require": {
"brick/math": "^0.8 || ^0.9",
"ext-json": "*",
"php": "^7.2 || ^8.0",
"ramsey/collection": "^1.0",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php80": "^1.14"
},
"replace": {
"rhumsaa/uuid": "self.version"
},
"require-dev": {
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.8",
"ergebnis/composer-normalize": "^2.15",
"mockery/mockery": "^1.3",
"moontoast/math": "^1.1",
"paragonie/random-lib": "^2",
"php-mock/php-mock": "^2.2",
"php-mock/php-mock-mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-mockery": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^8.5 || ^9",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.9"
},
"suggest": {
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
"ext-ctype": "Enables faster processing of character classification using ctype functions.",
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
},
"branch-alias": {
"dev-main": "4.x-dev"
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Ramsey\\Uuid\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"keywords": [
"guid",
"identifier",
"uuid"
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.2.3"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
"type": "tidelift"
}
],
"time": "2021-09-25T23:10:38+00:00"
},
{
"name": "symfony/cache",
"version": "v5.4.46",
@@ -2063,6 +2311,86 @@
],
"time": "2025-01-02T08:10:11+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v1.1.2",

View File

@@ -0,0 +1,16 @@
<?php
use Openguru\OpenCartFramework\Migrations\Migration;
return new class extends Migration {
public function up(): void
{
$sql = <<<SQL
ALTER TABLE `telecart_customers`
ADD COLUMN `tracking_id` VARCHAR(64) NOT NULL AFTER `oc_customer_id`;
SQL;
$this->database->statement($sql);
}
};

View File

@@ -3,6 +3,7 @@
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Support\Utils;
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseService;
if (! function_exists('table')) {
function db_table(string $name): string

View File

@@ -5,4 +5,5 @@ namespace Openguru\OpenCartFramework\TeleCartPulse;
final class PulseEvents
{
public const WEBAPP_OPEN = 'WEBAPP_OPEN';
public const ORDER_CREATED = 'ORDER_CREATED';
}

View File

@@ -33,7 +33,7 @@ class TeleCartPulseService
return;
}
$initData = Arr::get($data, 'webapp.initData');
$initData = Arr::get($data, 'payload.webapp.initData');
if (! $initData) {
return;
}
@@ -46,12 +46,16 @@ class TeleCartPulseService
try {
$decoded = $this->initDataDecoder->parseInitDataStringToArray($initData);
$startParam = Arr::get($decoded, 'start_param');
$startParam = Arr::get($decoded, 'start_param', '');
$deserialized = StartParamSerializer::deserialize($startParam);
if ($event === PulseEvents::WEBAPP_OPEN) {
$this->handleWebAppInit($data, $deserialized);
}
if ($event === PulseEvents::ORDER_CREATED) {
$this->handleOrderCreated($data, $deserialized);
}
} catch (ClientException $exception) {
$contents = (string)$exception->getResponse()->getBody();
$decoded = json_decode($contents, true);
@@ -108,4 +112,30 @@ class TeleCartPulseService
$client->post('events', compact('json'));
}
private function handleOrderCreated(array $data, array $deserialized): void
{
if (isset($deserialized['campaign_id'], $deserialized['tracking_id'])) {
$payload = [
'event' => PulseEvents::ORDER_CREATED,
'campaign_id' => $deserialized['campaign_id'],
'tracking_id' => $deserialized['tracking_id'],
'meta' => [
'domain' => Utils::getCurrentDomain(),
'version' => Arr::get($data, 'webapp.version'),
'platform' => Arr::get($data, 'webapp.platform'),
'order_id' => Arr::get($data, 'eventData.order_id'),
'currency' => Arr::get($data, 'eventData.currency'),
],
'timestamp' => Carbon::now('UTC')->toJSON(),
];
$dataToSend = [
'payload' => $payload,
'signature' => $this->payloadSigner->sign($payload),
];
$this->pushEvent($dataToSend);
}
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use Ramsey\Uuid\Uuid;
class TrackingIdGenerator
{
public static function generate(): string
{
return Uuid::uuid4()->toString();
}
}

View File

@@ -96,7 +96,7 @@ class ETLHandler
$query
->select([
new RawExpression('md5(telegram_user_id) AS tracking_id'),
'tracking_id',
'telegram_user_id' => 'tg_user_id',
'telecart_customers.oc_customer_id',
'is_premium',

View File

@@ -9,6 +9,7 @@ use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\TeleCartPulse\TrackingIdGenerator;
use Openguru\OpenCartFramework\Telegram\Enums\TelegramHeader;
use Openguru\OpenCartFramework\Telegram\Exceptions\DecodeTelegramInitDataException;
use Openguru\OpenCartFramework\Telegram\TelegramInitDataDecoder;
@@ -41,11 +42,15 @@ class TelegramCustomerHandler
public function saveOrUpdate(Request $request): JsonResponse
{
try {
$this->telegramCustomerService->saveOrUpdate(
$customer = $this->telegramCustomerService->saveOrUpdate(
$this->extractTelegramUserData($request)
);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
return new JsonResponse([
'data' => [
'tracking_id' => Arr::get($customer, 'tracking_id'),
],
], Response::HTTP_OK);
} catch (Throwable $e) {
$this->logger->error('Could not save telegram customer data', ['exception' => $e]);

View File

@@ -7,6 +7,7 @@ namespace App\Models;
use Carbon\Carbon;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Openguru\OpenCartFramework\TeleCartPulse\TrackingIdGenerator;
use RuntimeException;
class TelegramCustomer
@@ -81,6 +82,7 @@ class TelegramCustomer
{
$data['created_at'] = Carbon::now()->toDateTimeString();
$data['updated_at'] = Carbon::now()->toDateTimeString();
$data['tracking_id'] = TrackingIdGenerator::generate();
$success = $this->database->insert(self::TABLE_NAME, $data);

View File

@@ -23,10 +23,10 @@ class TelegramCustomerService
* Сохранить или обновить Telegram-пользователя
*
* @param array $telegramUserData Данные пользователя из Telegram.WebApp.initDataUnsafe
* @return void
* @return array
* @throws RuntimeException Если данные невалидны или не удалось сохранить
*/
public function saveOrUpdate(array $telegramUserData): void
public function saveOrUpdate(array $telegramUserData): array
{
$telegramUserId = $this->extractTelegramUserId($telegramUserData);
$telegramCustomerData = $this->prepareCustomerData($telegramUserData, $telegramUserId);
@@ -38,6 +38,8 @@ class TelegramCustomerService
} else {
$this->telegramCustomer->create($telegramCustomerData);
}
return $this->telegramCustomer->findByTelegramUserId($telegramUserId);
}
/**
@@ -49,7 +51,7 @@ class TelegramCustomerService
*/
private function extractTelegramUserId(array $telegramUserData): int
{
$telegramUserId = (int) Arr::get($telegramUserData, 'id');
$telegramUserId = (int)Arr::get($telegramUserData, 'id');
if ($telegramUserId <= 0) {
throw new RuntimeException('Telegram user ID is required and must be positive');
@@ -102,7 +104,7 @@ class TelegramCustomerService
]);
}
return (int) $customer['id'];
return (int)$customer['id'];
}
public function increaseOrdersCount(int $telecartCustomerId): void