Покинутий кошик Mailchimp: гайд для ледачих

Спочатку трохи просторікувань 🙂 Рано чи пізно перед будь-яким інтернет-магазином постає питання налаштування кинутого кошика. Статистика і відчуття втрачених грошей не щадять нікого.

Відсоток покинутих кошиків з 2006 по 2017

Відсоток покинутих кошиків на перший квартал 2018 року в розрізі індустрії:

При цьому, незважаючи на загальнодоступну статистику, більшість інтернет-магазинів не користуються доступними можливостями і не підключають кинутий кошик. Недавнє «домашнє» дослідження від EmailSoldiers наочно показує, що більша частина магазинів взагалі не заморочується про це.

Поточна статистика по підключеним кинутим кошикам

При цьому всі (і ми теж не святі) наганяють трафік, докручують рекламу та креативи, але навіть не намагаються повернути людину, яка зірвала в останній момент.

А адже і в першій ітерації можна отримати приріст замовлень, використовуючи лист без динамічного контенту, потрібно тільки налаштувати його. Всього один раз докласти зусиль, щоб у фоновому режимі це приносило гроші — чи це не казка?

Природно, лист з динамічним контентом, в яке підтягуються товари з кошика, може спрацювати краще. А, може, на вашу аудиторію крутіше подіє чергове кошеня з сумними очима. Або ви підтягнете рекомендовані товари до товарів у кошику і побачите, що з листів їх купують частіше і підвищують середній чек. Або у вас дійдуть руки зробити серію листів, з якої конверсія буде ще більше.

Можна покращувати і тестувати листи кинутого кошика до недосяжного ідеалу, але будь-який один раз налаштоване лист вже незрівнянно краще, ніж нічого.

Конверсія для кинутого кошика за даними RetailRocket

І ось ми з камрадом Артемом Александровим почали впровадження кошики з двох сторін.

Технічна реалізація

ТЗ на інтеграцію

Коротко опишемо суть завдання.

Завдання: підключити кинутий кошик для сайту ххх.хх з допомогою розсилочного сервісу Mailchimp

Видаємо всі необхідні матеріали.

Ключ API: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-usXX

Де взяти ключ?

→ Дивимося документацію в Mailchimp

ID листа, до якого підключаємо Store: XXXXXXXXXX

Де взяти ID аркуша?

У сервісі розсилок заздалегідь має бути створено лист. Як тільки API-запит отримано сервісом розсилок, відбувається автоматичне формування листа і додавання адресата в чергу для відправки.

Для нашого випадку ми обрали наступну логіку відправки кинутої кошика:
авторизований на сайті користувач додає товари в корзину, не здійснює транзакцію і не завершує замовлення, кошик залишається без змін 1 годину. Після цього відправляється запит в Mailchimp, в якому передається email, склад замовлення користувача, зображення товарів, ціна товарів і посилання на кошик користувача.

Верстка шаблону

При створенні автоматизації для кинутого кошика мейлчимп одразу питає, один буде лист або кілька, і пропонує вибрати підключений магазин.

Базові шаблони мч пропонує на вибір три штуки:

  1. Кинутий кошик з динамічними товарами
  2. Кинутий кошик з продуктовими рекомендаціями (треба налаштовувати окремо)
  3. Кинута кошик без товарів (просто текстове лист)

У кращих традиціях, якщо у вас є час, можна заверстать кошик самостійно.

Динамічні товари без стилів виглядають приблизно так (забудемо про відступи, вони некрасивенько тут відображаються):

<table>
<tbody> 
*|ABANDONED_CART:[$total=3]|*
<table>
<tbody> 
<tr>
<td>
<a href="*|CART:URL|*" title="*|PRODUCT:TITLE|*" target="_blank">
<img src="*|PRODUCT:IMAGE_URL|*">
</a>
</td>
<td>
*|PRODUCT:TITLE|* — *|PRODUCT:PRICE|*
</td>
</tr>
</tbody>
</table>
*|END:ABANDONED_CART|*
</tbody>
</table>
*|END:ABANDONED_CART|*
</tbody>
</table>

Здавалося б, якщо змінити цифру змінної *|ABANDONED_CART:[$total=3]|*, то в листі буде відображатися інша кількість товарів, але ні, поставте хоч 5, хоч 100, мч відмовляється показувати іншу кількість.

І, що теж трохи дивно, мінлива *|PRODUCT:PRICE|* замінюється на значення формату RUB288, і змінити це теж чомусь не можна, але про це пізніше.

Для різноманітності ми намагалися підставити ще й змінні з кількістю ігор та з загальною вартістю замовлення, які передаємо по api, але мейлчимп і тут сказав «ні». Що ж, хай буде так.

Читайте також  DNS-сервер не відповідає або Windows не вдається зв'язатися з пристроєм або ресурсом основний DNS-сервер

Слово програмісту 🙂

З мэйлчимпом був історичний досвід інтеграції з їх стороннім сервісом mandrill, там все просто і зрозуміло. Дуже дружелюбна документація (звичайно ж, англійською), але не виникло ніяких шорсткостей і запрацювало з першого стусана.

Також у нас на сайті було запроваджено механізм передплати через спеціальні форми. Ось там ми повною мірою відчули недомовленість і двозначність документації мэйлчимпа. Англійська не є проблемою для досвідченого розробника, а от знання клингонского діалекту не мається на увазі за замовчуванням.

Вихідні дані такі: мова php7 і фреймворк yii2, який сильно обріс вже своєю екосистемою. Тобто у нас вже 6 невеликих проектів, які намагаються використовувати загальні компоненти як на бэкенде, так і на фронтенде. Відповідно, реалізація будь-якої задачі вимагає вирішувати її проектонезависимо, але це не означає фрэймворконезависимость, т. к. за це доводиться платити человекочасами, яких завжди дефіцит.

Отримавши завдання на інтеграцію, треба насамперед озирнутися. Що нам дано? По-перше, сервіс мэйлчимп, з яким треба подружитися. Йдемо на гітхаб і бачимо, що там досить багато реалізацій. Але вибір простий — у самого популярного пакету 1.5 до зірок (drewm/mailchimp-api).

Пакет дає просту обгортку над rest-взаємодією з мэйлчимпом. Нам залишається тільки обкласти це своєю логікою.

По-друге, нам дана документація. Виходячи з документації, у нас є ресурс Store з вкладеними ресурсами: Cart, Customer, Order, Product, Promo rule. Для залишеною без кошика рекомендованих товарів нам знадобляться тільки Product, Cart і Customer. Cart в свою чергу складається з набору Cart line, а Product містить Product variants.

Ми декомпозировали завдання наступним чином:

  1. Завантажити дані по магазину в ресурс Stores
  2. Завантажити всі доступні до покупки товари в Products
  3. Налаштувати завантаження кошиків з користувачами за розкладом

Ок, поїхали. Першим ділом беремося за сутність «магазин». Ми вирішили відразу використовувати тестовий і бойовий варіант магазину і, в залежності від змінної оточення, що відповідає за режим, ми працювали або з одним магазином, або з іншим.

Щоб завантажити дані по магазину, ми стукаємо post-запитом за адресою /ecommerce/stores з наступним набором параметрів:

[
 'id' => 'dev.***.ru',
 'list_id' => '****',
 'name' => '*** - test',
 'domain' => 'dev.***.ru',
 'email_address' => 'admin@***.ru',
 'currency_code' => 'RUB',
 'primary_locale' => 'uk',
 'money_format' => '₽',
]

Параметрів дещо більше, але все залежить від потреб. Оскільки ми не збиралися використовувати контактні дані магазину в листах, то не заповнили поля phone, address, timezone і т. п.
Але нас чекав невеличкий сюрприз. Поле money_format ніби спеціально створено для можливості представити ціну в зручному нам форматі. Але при побудові шаблону кинутого кошика мэйлчимп наполегливо підставляє RUB перед числом. Мэйлчимп, перестань!

Після завантаження ми можемо перевірити за допомогою get-запиту за адресою /ecommerce/stores, щоб побачити всі завантажені магазини, або /ecommerce/stores/{id} для отримання даних по конкретному магазину.

Тепер можна додавати інші дані, т. к. Store є кореневою точкою в нашому дереві даних, оскільки всі інші дані ми будемо завантажувати в певний магазин.

Так-с, щоб МЧ міг підставляти товари в кинутий кошик, треба згодувати йому ці товари. Для цього у нас є адреса /ecommerce/stores/{store_id}/products, куди ми відправляємо post-запити на створення продуктів в системі.


[
 'id' => '742',
 'title' => 'Каструля',
 'handle' => 'kastrulya',
 'url' => 'http://***.ru/catalog/kastrulya/',
 'description' => 'Каструля — незамінна річ на кухні. Купивши каструлю, ви зміните своє життя в кращу сторону. Ви зрозумієте, що неможливо прожити без цієї речі і дня! В кожний будинок по каструлі і нехай ніхто не піде скривдженим!',
 'type' => 'Посуд',
 'vendor' => 'Роги і Копита',
 'image_url' => 'http://***.ua/images/742/product.png',
 'variants' => [
[
 'id' => '742',
 'title' => 'Каструля',
 'url' => 'http://***.ru/catalog/kastrulya/',
 'price' => 890,
 'sku' => 'KA453',
 'inventory_quantity' => 1000,
 'image_url' => 'http://***.ua/images/742/product.png',
 'visibility' => 'visible',
],
],
]

Що тут примітного? Ну по-перше, кожен товар має складатися хоча б з одної товарного пропозиції. По суті Product — це якийсь контейнер для завантаження товарних пропозицій. Причому, id товару і товарної пропозиції можуть перетинатися, оскільки це різні ресурси в api МЧ.

Читайте також  Як відкрити ІП в Німеччині, якщо ти програміст, і не набити гуль

І ось тут почалася недомовленість документації МЧ. Доводилося здогадуватися, нехай це було нескладно, але ж можна було зробити нормально, а не як завжди.

Поле handle було описано як «the handle of a product». Ок, порадившись, ми вирішили, що це частина url, що відноситься до самого продукту (чпу). Але це підтвердилося тільки в ході тестів.

Також до товару можна прикріпити масив картинок, але ми вирішили, що нам не знадобиться і тому вантажили тільки головне зображення як до товару, так і до кожної товарної пропозиції.

І тут у нас виникла проблема, товари чомусь не відображалися в темплейтах мейлчимпа.

Почали ритися в доці по Product. І знайшли поле visibility з розкішним описом:

Ну ок, тип String! А що туди можна передавати? Чому не можна описати всі можливі значення?! Адже Я можу туди відправити, наприклад, «show me pls!».

Благо є приклад запиту!

Ну, це не скасовує проблему. Адже Я так і не знаю, які ще можуть бути значення, які можуть виявитися корисними.

Все, з цим впоралися! Тепер email-маркетолог може переконатися в наявності товарів в системі через побудову шаблону з участю товарів або все через ті ж get-запити за допомогою консолі.

Далі перед нами стоїть завдання завантаження покинутих кошиків в МЧ. Спочатку в голову прийшло 2 варіанти:

  1. При кожній зміні кошика (додавання/видалення товару), ми повторюємо цю дію в МЧ. З мінусів — відразу напрошується величезна кількість запитів до зовнішнього сервісу.
  2. Раз в n хвилин дивитися кошики, які не змінювалися більше години тому. Після чого відправляємо їх в МЧ. Проблема тільки одна — стежити за кошиками, які були відновлені після того, як подалися в МЧ.

Для початку робимо запит в нашу базу даних (далі БД) за нашими даними в вікні від 1 години до 3. Чому 3? Через годину після останньої зміни ми відправляємо кошик в систему. В МЧ налаштований мінімально можливий інтервал відправки кошика — 1 годину. Тому в теорії через 2 години ± 5 хвилин відбудеться відправка листа. Так що 3 години — це величина навіть з запасом.

Отримавши дані з БД, ми робимо get-запит за адресою /ecommerce/stores/{store_id}/carts. Таким чином ми отримуємо всі кошики, які сидять в системі e-commerce і чекають своєї черги на відправку (або вже відправлені). Для чого нам це потрібно? Потрібно для синхронізації з нашими даними. Ми відправимо всі кошики, отримані з БД, але нам потрібно видалити ті, які вже не знаходяться в проміжку 1-3 години. Після 3х — вже неактуальні дані. До години — кошики, які могли знову відновити, або оформити замовлення.

Для видалення нам треба просто знайти різницю між двома масивами/колекціями кошиків.
Отримавши кошики, які необхідно видалити, ми відправляємо delete-запит /ecommerce/stores/{store_id}/carts/{cart_id}.

Далі беремо кошика для завантаження і циклом відправляємо їх post-запитів в систему.

Параметри кошика виглядають так:


[
 'id' => '1207',
 'customer' =>
[
 'id' => '25',
 'email_address' => 'email@example.com',
 'opt_in_status' => false,
],
 'currency_code' => 'RUB',
 'order_total' => 1597,
 'checkout_url' = > 'http://***.ru/cart/abandoned/?cart=eyJpdGVtcyI6eyI1OTgwIjoxLCIzNDA0ijoxlci3nzmiojesijkwntgiojesijkwoteiojesije4odciojesijc4nci6mswintexmsi6mswioda1myi6mswimtk0msi6mswintq0nsi6mswinzk1nci6mywiota2nyi6ncwiota2nsi6ncwinzg0myi6mswiota2nii6m30sinbyb21vy29kzsi6bnvsbh0%253D',
 'lines' => [
 0 => [
 'id' => '123',
 'product_id' => '5980',
 'product_variant_id' => '5980',
 'кількість' => 1,
 'price' => 841,
],
 1 => [
 'id' => '124',
 'product_id' => '3404',
 'product_variant_id' => '3404',
 'кількість' => 1,
 'price' => 756,
],
],
]

І знову наша улюблена рубрика «здогадайся, як працюють ці поля». Наприклад, методом наукового тику було виявлено, що можна не створювати покупця окремим запитом. Треба передати мінімально необхідний набір полів, і він автоматично створиться, якщо його не було в системі. У нашому випадку ми обмежилися id, email, opt_in_status. Останній параметр відповідає за стан передплати юзера в нашому листі. Якщо він true, то це означає стан subscribed, в іншому випадку transactional.

Читайте також  Проектування дашбордів для веб-аналітики e-commerce сайту. Частина 2: Email-розсилки. Операційний дашборд

Список товарів без проблем завантажується через масив Cart Lines, який в свою чергу є ресурсом сутності Cart. Тобто ми можемо окремо управляти цим набором з допомогою rest-запитів.

Ну ось начебто і все? А ось і ні.

При тестуванні ми звернули увагу, що відправивши одну корзину, вона відправляється лише раз. Хоча ми її видаляли з системи і завантажували заново. Ніде нічого не сказано жодного слова! У підсумку досвідченим шляхом, ми взяли за основу гіпотезу, що кошик з одним і тим же id може бути відправлений лише один раз.

Тут я повністю згоден з авторами, що це обмеження вберігає нас від заспамливания користувачів. Але напишіть про це! Не потрібні нам квести, нам вистачає своїх містичних помилок в коді, який ми пишемо.

Після того, як ми все це зробили, МЧ почав надсилати нам гарні листи про кинуті кошикиу. І тут з’явилося друге питання. Якщо юзер, кинув кошик і повернувся з листа в свій же аккаунт, тобто він був авторизований у момент переходу по посиланню, то він потрапить в свою корзину без проблем. А так виходить, що лист каже тобі«! візьми свій кошик назад!», а ми при переході йому кажемо «ой, нічого немп! Ми нічого не чіпали! Воно саме!»

Було вирішено кодувати склад кошика в рядок і передавати в checkout_url при відправці кошика в МЧ. А при переході на сайт ловити цей рядок, декодувати і накидати всі товари в корзину, не забувши перед цим її повністю обнулити.

Таким чином, в який би браузер ми не відправили юзера, він отримує свою корзину, як ми і обіцяли. Єдиний мінус, що йому залишається тільки авторизуватись. Але авторизація через посилання — це проблема, та й взагалі справа небезпечна, в першу чергу для наших клієнтів.
Що в підсумку? В принципі, проблем особливих не було при реалізації, так як дуже часто виручали відповіді МЧ при помилках, пов’язаних з валідацією переданих полів. Але їх було б ще менше, якби вони нормально по-людськи описали всі ці тонкощі роботи МЧ і більш докладно описали б поля.

Настроювання звіту в Google.Analytics

Щоб відстежувати успіх всієї операції, доведеться налаштувати і періодично поглядати звіт в аналітиці. Уявімо, що у нас у всіх, як у дорослих, підключений ecommerce, інакше дива не вийде 🙂

Щоб зібрати новий звіт під кинуті кошики йдемо в «Мої звіти»:

Далі «Додати звіт»:

А після додаємо параметри, які будемо відслідковувати. Ми вирішили, що будемо дивитися на кинуті кошики в розрізі міст, у вас може бути інше бачення.

У мейлчимпа стандартною кампанією для кинутого кошика є ABANDONED_CART_EMAIL, підставляємо її у фільтр і отримуємо звіт.

That’s all forks!

Тепер у вас налаштована відправка кинутого кошика і звіт, по яким ви можете дивитися вихлоп з неї. І тестуйте, тестуйте, тестуйте! 😉

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *