Модификации

Форма пожертвования для Tilda: Mixplat + UniSender + электронная коммерция

Формы пожертвования

Инструкция

Эта модификация создаёт полноценную форму пожертвования для Tilda. Она позволяет принимать разовые и ежемесячные пожертвования через Mixplat, передавать контакты успешных доноров в UniSender, фиксировать действия пользователя в Яндекс Метрике и отправлять данные об успешном пожертвовании в ecommerce.

Перед публикацией форму обязательно нужно настроить под свой проект.
  1. В самом начале кода найдите блок «НАСТРОЙКИ ИНТЕГРАЦИЙ», вырежьте его из основного кода и вставьте в Tilda в «Настройки сайта → Ещё → HTML-код для вставки внутрь HEAD». Остальной код целиком вставьте на страницу в отдельный блок T123 «HTML-код».
  2. В блоке для HEAD замените MIXPLAT_WIDGET_KEY на ключ виджета Mixplat, UNISENDER_API_KEY на API-ключ UniSender, UNISENDER_LIST_ID_MONTHLY и UNISENDER_LIST_ID_ONETIME на ID списков для регулярных и разовых доноров, а YANDEX_METRIKA_ID — на номер своего счётчика Яндекс Метрики.
  3. В начале CSS настройте внешний вид формы через переменные --nf-bg, --nf-card, --nf-text, --nf-muted, --nf-field, --nf-border, --nf-accent и --nf-accent-hover. Скругления карточки и полей задаются через --nf-radius и --nf-field-radius.
  4. В .nf-support__title замените заголовок формы, в .nf-support__subtitle — короткое описание, а в .nf-support__amount-note — пояснение под выбором суммы.
  5. В блоке .value_field укажите нужные готовые суммы. Для каждой кнопки обязательно одновременно меняйте value у input и видимую сумму внутри label: например, для 700 ₽ должно быть value="700" и текст 700 ₽. Атрибут checked оставьте только у той суммы, которая должна быть выбрана по умолчанию.
  6. Произвольная сумма вводится через поле #custom_price. Значение min="1" задаёт минимальное значение самого HTML-поля; если нужен обязательный минимальный размер пожертвования выше 1 ₽, потребуется также скорректировать JavaScript-валидацию.
  7. Поля #name и #email используются платёжной логикой и UniSender, поэтому их ID не меняйте. Тексты внутри placeholder можно заменить на свои.
  8. В функции updateRecurrentState() замените тексты, которые показываются после выбора разового или ежемесячного пожертвования. Саму конструкцию if (isMonthly) и блок subscribe_confirmation не удаляйте — они отвечают за подтверждение регулярного списания.
  9. В ссылках href="/policy" и href="/oferta" укажите адреса своих юридических документов, а текст основной кнопки при необходимости измените в value="ПОМОЧЬ".
  10. В объекте const options при необходимости замените description: 'Благотворительное пожертвование' на своё описание платежа. Остальные параметры Mixplat без необходимости не изменяйте.
  11. После успешной оплаты разовый донор автоматически отправляется в список UNISENDER_LIST_ID_ONETIME, а регулярный — в UNISENDER_LIST_ID_MONTHLY. Убедитесь, что в HEAD указаны существующие ID нужных списков UniSender.
  12. После успешного платежа пользователь перенаправляется на /success, а после ошибки или закрытия платёжного окна — на /fail. Замените эти адреса на страницы успешного и неуспешного платежа своего сайта.
  13. Не меняйте без необходимости технические значения name="price", name="recurrent", value="one-time", value="monthly", ID элементов формы и callbacks Mixplat — на них завязана работа JavaScript.
  14. Перед публикацией протестируйте разовое и ежемесячное пожертвование, все готовые суммы, произвольную сумму, валидацию обязательных полей, успешную оплату, ошибку и закрытие Mixplat. После успешной оплаты отдельно проверьте попадание контакта в нужный список UniSender, цели Метрики, ecommerce и переход на страницу /success.
Рекомендации:
Настройки в HEAD:
  • MIXPLAT_WIDGET_KEY — ключ Mixplat.
  • UNISENDER_API_KEY — API-ключ UniSender.
  • UNISENDER_LIST_ID_MONTHLY — список регулярных доноров.
  • UNISENDER_LIST_ID_ONETIME — список разовых доноров.
  • YANDEX_METRIKA_ID — ID счётчика Метрики.

Цели Яндекс Метрики:
  • donation_visible — форма появилась в области видимости.
  • donation_attempt — пользователь попытался перейти к оплате.
  • donation_success — успешный платёж.
  • donation_closed — пользователь закрыл виджет.
  • donation_fail — ошибка платежа.

Электронная коммерция: после успешного платежа отправляется событие ecommerce_payment_success с суммой и типом пожертвования. Если на сайте уже настроена собственная ecommerce-логика, проверьте совместимость с существующим dataLayer.

Что можно менять свободно: цвета, шрифты, размеры, скругления, отступы, тексты, подписи, готовые суммы, ссылки на документы, описание платежа и страницы /success и /fail.

Что лучше не трогать: технические ID, name и value radio-полей, создание объекта Mixplat, recurrent_payment, функции UniSender, callbacks успешного/неуспешного платежа и последовательность отправки аналитики.
<!-- Адаптивная форма пожертвования для Tilda с Mixplat, UniSender, Яндекс Метрикой и ecommerce. Источник: bulgkate.ru -->


<!-- =========================================================
     НАСТРОЙКИ ИНТЕГРАЦИЙ
     ЭТОТ БЛОК НУЖНО ВСТАВИТЬ В HEAD САЙТА:
     Настройки сайта → Ещё → HTML-код для вставки внутрь HEAD

     Перед публикацией замените значения на свои.
     ========================================================= -->

<script>
    window.MIXPLAT_WIDGET_KEY = 'YOUR_MIXPLAT_WIDGET_KEY';

    window.UNISENDER_API_KEY = 'YOUR_UNISENDER_API_KEY';

    window.UNISENDER_LIST_ID_MONTHLY = 4;
    window.UNISENDER_LIST_ID_ONETIME = 3;

    window.YANDEX_METRIKA_ID = 11;
</script>

<!-- =========================================================
     КОНЕЦ БЛОКА ДЛЯ HEAD
     Основной код формы начинается ниже.
     ========================================================= -->
     
     
<style>
/* =========================================================
   ОСНОВНЫЕ НАСТРОЙКИ
   ========================================================= */

.nf-support {
    --nf-bg: #f4f3ee;
    --nf-card: #fbfbf8;
    --nf-text: #29322d;
    --nf-muted: #707a74;
    --nf-field: #eeefe9;
    --nf-border: #d9ddd6;

    --nf-accent: #687c70;
    --nf-accent-hover: #596c61;
    --nf-accent-soft: #e4e9e3;
    --nf-accent-text: #ffffff;

    --nf-radius: 24px;
    --nf-field-radius: 14px;

    width: 100%;
    padding: 88px 24px;

    color: var(--nf-text);
    background: var(--nf-bg);

    font-family:
        var(--nf-font, "TildaSans"),
        Arial,
        sans-serif;
}

.nf-support,
.nf-support *,
.nf-support *::before,
.nf-support *::after {
    box-sizing: border-box;
}

.nf-support input,
.nf-support button,
.nf-support label,
.nf-support a {
    font-family: inherit;
}


/* =========================================================
   КОНТЕЙНЕР
   ========================================================= */

.nf-support__inner {
    width: min(590px, 100%);
    margin: 0 auto;
}


/* =========================================================
   ФОРМА
   ========================================================= */

.nf-support .donation {
    width: 100%;
    margin: 0;
    padding: 0;
}

.nf-support .donation-form {
    width: 100%;
    margin: 0;

    overflow: hidden;

    border: 1px solid var(--nf-border);
    border-radius: var(--nf-radius);

    background: var(--nf-card);
    color: var(--nf-text);

    box-shadow:
        0 18px 50px rgba(51, 61, 55, 0.07),
        0 3px 10px rgba(51, 61, 55, 0.035);
}

.nf-support .donation_form_wrapper {
    width: 100%;
    padding: 38px 40px 40px;
}

.nf-support .inputBlock {
    width: 100%;
    margin: 0;
    padding: 0;
}

.nf-support input[type="radio"] {
    display: none;
}


/* =========================================================
   ЗАГОЛОВОК
   ========================================================= */

.nf-support__heading {
    margin: 0 0 30px;
}

.nf-support__title {
    margin: 0;

    color: var(--nf-text);

    font-size: clamp(30px, 4vw, 38px);
    font-weight: 600;
    line-height: 1.08;
    letter-spacing: -0.025em;
}

.nf-support__subtitle {
    max-width: 470px;

    margin: 12px 0 0;

    color: var(--nf-muted);

    font-size: 16px;
    font-weight: 400;
    line-height: 1.5;
}


/* =========================================================
   РАЗОВО / ЕЖЕМЕСЯЧНО
   ========================================================= */

.nf-support .recurrent_field {
    display: grid;
    grid-template-columns: 1fr 1fr;

    width: 100%;
    margin: 0 0 18px;
    padding: 4px;

    border: 1px solid var(--nf-border);
    border-radius: 999px;

    background: var(--nf-field);
}

.nf-support .recurrent_field .label-for-check {
    display: flex;
    align-items: center;
    justify-content: center;

    min-width: 0;
    height: 50px;

    margin: 0;
    padding: 0 16px;

    border: 0;
    border-radius: 999px;

    background: transparent;
    color: var(--nf-muted);

    font-size: 15px;
    font-weight: 500;
    line-height: 1.2;
    text-align: center;

    cursor: pointer;

    transition:
        background-color 0.18s ease,
        color 0.18s ease,
        box-shadow 0.18s ease;
}

.nf-support #recurrent_choice_2:checked + .one-time,
.nf-support #recurrent_choice_1:checked + .monthly {
    background: var(--nf-card);
    color: var(--nf-text);

    box-shadow:
        0 2px 9px rgba(47, 58, 51, 0.08);
}


/* =========================================================
   ПОЯСНЕНИЕ ТИПА ПЛАТЕЖА
   ========================================================= */

.nf-support .recurrent_hint {
    min-height: 68px;

    margin: 0 0 30px;
    padding: 0 2px;

    color: var(--nf-muted);

    font-size: 14px;
    font-weight: 400;
    line-height: 1.5;
}


/* =========================================================
   СУММА
   ========================================================= */

.nf-support .descr {
    margin: 0 0 13px;

    color: var(--nf-text);

    font-size: 17px;
    font-weight: 500;
    line-height: 1.3;
}

.nf-support .value_field {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));

    gap: 9px;

    width: 100%;
    margin: 0;
    padding: 0;
}

.nf-support .value_field > .label-for-check {
    display: flex;
    align-items: center;
    justify-content: center;

    min-width: 0;
    height: 48px;

    margin: 0;
    padding: 0 8px;

    border: 1px solid var(--nf-border);
    border-radius: 999px;

    background: var(--nf-card);
    color: var(--nf-text);

    font-size: 15px;
    font-weight: 500;
    line-height: 1;

    white-space: nowrap;
    cursor: pointer;

    transition:
        background-color 0.18s ease,
        border-color 0.18s ease,
        color 0.18s ease,
        transform 0.18s ease;
}

@media (hover: hover) {
    .nf-support .value_field > .label-for-check:hover {
        border-color: var(--nf-accent);
        background: var(--nf-accent-soft);
    }
}

.nf-support
.value_field
.check-with-label:checked
+ .label-for-check {
    border-color: var(--nf-accent);
    background: var(--nf-accent);
    color: var(--nf-accent-text);
}


/* =========================================================
   ДРУГАЯ СУММА
   ========================================================= */

.nf-support .value_field > .custom-label {
    padding: 0;
    overflow: hidden;
}

.nf-support .custom-label .custom-input {
    display: block;

    width: 100%;
    height: 46px;

    margin: 0;
    padding: 0 8px;

    border: 0;
    outline: 0;

    background: transparent;
    color: inherit;

    font-size: 15px;
    font-weight: 500;
    line-height: 46px;
    text-align: center;

    appearance: textfield;
    -moz-appearance: textfield;
}

.nf-support
.custom-label
.custom-input::-webkit-inner-spin-button,
.nf-support
.custom-label
.custom-input::-webkit-outer-spin-button {
    margin: 0;
    -webkit-appearance: none;
}

.nf-support .custom-input::placeholder {
    color: inherit;
    opacity: 0.7;
}

.nf-support .custom-label.custom-active {
    border-color: var(--nf-accent);
    background: var(--nf-accent);
    color: var(--nf-accent-text);
}


/* =========================================================
   ТЕКСТ ПОД СУММОЙ
   ========================================================= */

.nf-support__amount-note {
    margin: 14px 2px 29px;

    color: var(--nf-muted);

    font-size: 13px;
    font-weight: 400;
    line-height: 1.5;
}


/* =========================================================
   ИМЯ / EMAIL
   ========================================================= */

.nf-support .name_field,
.nf-support .email_field {
    display: block;
    width: 100%;
}

.nf-support .name_field {
    margin-bottom: 10px;
}

.nf-support .email_field {
    margin-bottom: 15px;
}

.nf-support .name_field input[type="text"],
.nf-support .email_field input[type="email"] {
    display: block;

    width: 100%;
    height: 52px;

    margin: 0;
    padding: 0 18px;

    border: 1px solid transparent;
    border-radius: var(--nf-field-radius);

    outline: none;

    background: var(--nf-field);
    color: var(--nf-text);

    font-size: 15px;
    font-weight: 400;
    line-height: 1;

    transition:
        background-color 0.18s ease,
        border-color 0.18s ease,
        box-shadow 0.18s ease;
}

.nf-support .name_field input::placeholder,
.nf-support .email_field input::placeholder {
    color: #8a938d;
    opacity: 1;
}

.nf-support .name_field input:focus,
.nf-support .email_field input:focus {
    border-color: #abb8b0;
    background: #ffffff;

    box-shadow:
        0 0 0 3px rgba(104, 124, 112, 0.09);
}


/* =========================================================
   ЧЕКБОКСЫ
   ========================================================= */

.nf-support .subscribe_confirmation,
.nf-support .checkbox-row {
    display: flex;
    align-items: flex-start;

    width: 100%;
    padding: 0;

    color: var(--nf-muted);

    font-size: 12px;
    font-weight: 400;
    line-height: 1.45;
    text-align: left;
}

.nf-support .subscribe_confirmation {
    margin: 2px 0 12px;
}

.nf-support .checkbox-row {
    margin: 0 0 24px;
}

.nf-support .subscribe_confirmation input,
.nf-support .checkbox-row input {
    flex: 0 0 auto;

    width: 16px;
    height: 16px;

    margin: 1px 9px 0 0;
    padding: 0;

    accent-color: var(--nf-accent);
}

.nf-support .subscribe_confirmation label,
.nf-support .checkbox-row label {
    margin: 0;
    padding: 0;

    color: inherit;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
}

.nf-support .checkbox-row a {
    color: inherit !important;

    text-decoration: underline !important;
    text-decoration-thickness: 1px !important;
    text-underline-offset: 2px;
}


/* =========================================================
   КНОПКА
   ========================================================= */

.nf-support .submitBlock {
    width: 100%;
    margin: 0;
    padding: 0;
}

.nf-support #donation_form .donate-button {
    display: block;

    width: 100%;
    min-width: 0;
    height: 56px;

    margin: 0;
    padding: 0 28px;

    border: 0;
    border-radius: var(--nf-field-radius);

    background: var(--nf-accent);
    color: var(--nf-accent-text);

    font-size: 16px;
    font-weight: 600;
    line-height: 1;

    cursor: pointer;

    transition:
        background-color 0.18s ease,
        transform 0.18s ease,
        box-shadow 0.18s ease;
}

@media (hover: hover) {
    .nf-support #donation_form .donate-button:hover {
        background: var(--nf-accent-hover);

        box-shadow:
            0 7px 18px rgba(74, 94, 82, 0.16);
    }
}

.nf-support #donation_form .donate-button:active {
    transform: scale(0.99);
}


/* =========================================================
   TABLET
   ========================================================= */

@media (max-width: 767px) {
    .nf-support {
        padding: 54px 20px;
    }

    .nf-support__inner {
        width: min(580px, 100%);
    }

    .nf-support .donation_form_wrapper {
        padding: 32px 30px 34px;
    }

    .nf-support__title {
        font-size: clamp(29px, 6vw, 35px);
    }
}


/* =========================================================
   MOBILE
   ========================================================= */

@media (max-width: 599px) {
    .nf-support {
        padding: 28px 12px;

        --nf-radius: 20px;
        --nf-field-radius: 13px;
    }

    .nf-support .donation_form_wrapper {
        padding: 26px 18px 28px;
    }

    .nf-support__heading {
        margin-bottom: 25px;
    }

    .nf-support__title {
        font-size: 29px;
        line-height: 1.1;
    }

    .nf-support__subtitle {
        margin-top: 9px;

        font-size: 15px;
        line-height: 1.45;
    }

    .nf-support .recurrent_field {
        margin-bottom: 16px;
    }

    .nf-support .recurrent_field .label-for-check {
        height: 46px;

        padding: 0 8px;

        font-size: 14px;
    }

    .nf-support .recurrent_hint {
        min-height: 0;

        margin-bottom: 26px;

        font-size: 13px;
        line-height: 1.48;
    }

    .nf-support .descr {
        margin-bottom: 11px;

        font-size: 16px;
    }

    .nf-support .value_field {
        grid-template-columns:
            repeat(3, minmax(0, 1fr));

        gap: 8px;
    }

    .nf-support .value_field > .label-for-check {
        height: 46px;

        padding: 0 5px;

        font-size: 14px;
    }

    .nf-support .value_field > .custom-label {
        grid-column: 1 / -1;
    }

    .nf-support .custom-label .custom-input {
        height: 44px;

        font-size: 14px;
        line-height: 44px;
    }

    .nf-support__amount-note {
        margin:
            13px 2px 25px;

        font-size: 12px;
    }

    .nf-support .name_field input[type="text"],
    .nf-support .email_field input[type="email"] {
        height: 50px;

        padding: 0 16px;

        font-size: 15px;
    }

    .nf-support .subscribe_confirmation,
    .nf-support .checkbox-row {
        font-size: 11px;
        line-height: 1.45;
    }

    .nf-support .checkbox-row {
        margin-bottom: 21px;
    }

    .nf-support #donation_form .donate-button {
        height: 54px;

        font-size: 16px;
    }
}


/* =========================================================
   VERY SMALL
   ========================================================= */

@media (max-width: 359px) {
    .nf-support {
        padding-right: 8px;
        padding-left: 8px;
    }

    .nf-support .donation_form_wrapper {
        padding-right: 14px;
        padding-left: 14px;
    }

    .nf-support .recurrent_field .label-for-check {
        font-size: 13px;
    }

    .nf-support .value_field {
        gap: 6px;
    }

    .nf-support .value_field > .label-for-check {
        font-size: 13px;
    }
}
</style>


<section class="nf-support">
    <div class="nf-support__inner">

        <div class="donation" id="help">

            <form
                id="donation_form"
                class="donation-form"
                method="post"
                action=""
                novalidate
            >

                <div class="donation_form_wrapper">

                    <!-- ЗАГОЛОВОК -->

                    <div class="nf-support__heading">
                        <h2 class="nf-support__title">
                            Сделать пожертвование
                        </h2>

                        <p class="nf-support__subtitle">
                            Выберите удобный формат и сумму поддержки.
                        </p>
                    </div>


                    <!-- РАЗОВО / ЕЖЕМЕСЯЧНО -->

                    <div class="inputBlock recurrent_field">

                        <input
                            type="radio"
                            id="recurrent_choice_2"
                            name="recurrent"
                            value="one-time"
                            class="check-with-label"
                            checked
                        >

                        <label
                            for="recurrent_choice_2"
                            class="label-for-check one-time"
                        >
                            Помочь разово
                        </label>


                        <input
                            type="radio"
                            id="recurrent_choice_1"
                            name="recurrent"
                            value="monthly"
                            class="check-with-label"
                        >

                        <label
                            for="recurrent_choice_1"
                            class="label-for-check monthly"
                        >
                            Помогать ежемесячно
                        </label>

                    </div>


                    <div
                        id="recurrent_hint"
                        class="recurrent_hint"
                    ></div>


                    <!-- СУММА -->

                    <div class="descr">
                        Сумма пожертвования
                    </div>


                    <div class="inputBlock value_field">

                        <input
                            type="radio"
                            id="value_choice_1"
                            name="price"
                            value="500"
                            class="check-with-label"
                        >

                        <label
                            for="value_choice_1"
                            class="label-for-check"
                        >
                            500 ₽
                        </label>


                        <input
                            type="radio"
                            id="value_choice_2"
                            name="price"
                            value="1500"
                            class="check-with-label"
                            checked
                        >

                        <label
                            for="value_choice_2"
                            class="label-for-check"
                        >
                            1500 ₽
                        </label>


                        <input
                            type="radio"
                            id="value_choice_3"
                            name="price"
                            value="3000"
                            class="check-with-label"
                        >

                        <label
                            for="value_choice_3"
                            class="label-for-check"
                        >
                            3000 ₽
                        </label>


                        <input
                            type="radio"
                            id="value_choice_custom"
                            name="price"
                            value="custom"
                            class="check-with-label"
                        >

                        <label
                            for="value_choice_custom"
                            class="label-for-check custom-label"
                        >
                            <input
                                type="number"
                                id="custom_price"
                                name="custom_price"
                                placeholder="Другая сумма"
                                class="custom-input"
                                min="1"
                                inputmode="numeric"
                            >
                        </label>

                    </div>


                    <div class="nf-support__amount-note">
                        Любая сумма имеет значение и помогает проекту продолжать работу.
                    </div>


                    <!-- ИМЯ -->

                    <div class="inputBlock name_field">
                        <input
                            type="text"
                            id="name"
                            name="name"
                            placeholder="Ваше имя"
                            required
                        >
                    </div>


                    <!-- EMAIL -->

                    <div class="inputBlock email_field">
                        <input
                            type="email"
                            id="email"
                            name="email"
                            placeholder="Ваш email"
                            required
                        >
                    </div>


                    <!-- ПОДТВЕРЖДЕНИЕ РЕГУЛЯРНОГО ПОЖЕРТВОВАНИЯ -->

                    <div
                        class="subscribe_confirmation"
                        id="subscribe_confirmation_block"
                        style="display:none;"
                    >
                        <input
                            type="checkbox"
                            id="subscribe_confirmation"
                            name="subscribe_confirmation"
                            value="1"
                        >

                        <label for="subscribe_confirmation">
                            Я понимаю, что пожертвование будет повторяться каждый месяц
                        </label>
                    </div>


                    <!-- СОГЛАСИЯ -->

                    <div class="checkbox-row">

                        <input
                            type="checkbox"
                            id="privacy"
                            name="privacy"
                            value="1"
                            required
                        >

                        <label for="privacy">
                            Я соглашаюсь с
                            <a href="/policy" target="_blank">политикой конфиденциальности</a>
                            и с 
                            <a href="/oferta" target="_blank">офертой</a>.
                        </label>

                    </div>


                    <!-- ОПЛАТА -->

                    <div class="inputBlock submitBlock">
                        <input
                            type="submit"
                            value="ПОМОЧЬ"
                            class="donate-button"
                        >
                    </div>

                </div>
            </form>

        </div>
    </div>
</section>


<!-- MIXPLAT + JQUERY -->

<script src="https://cdn.mixplat.ru/widget/v3/widget.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>


<script>
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}


function isInViewport(el) {
    const rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <=
            (window.innerHeight ||
                document.documentElement.clientHeight) &&
        rect.right <=
            (window.innerWidth ||
                document.documentElement.clientWidth)
    );
}


function sendMetrikaGoal(goalName, params) {
    if (
        typeof ym === 'function' &&
        window.YANDEX_METRIKA_ID
    ) {
        ym(
            window.YANDEX_METRIKA_ID,
            'reachGoal',
            goalName,
            params || {}
        );
    }
}


document.addEventListener(
    'scroll',
    function () {

        const donationForm =
            document.querySelector('#donation_form');

        if (
            donationForm &&
            isInViewport(donationForm)
        ) {
            if (
                !window.__donation_form_visible_logged
            ) {
                sendMetrikaGoal(
                    'donation_visible'
                );

                window.__donation_form_visible_logged =
                    true;
            }
        }
    },
    {
        passive: true
    }
);


function addToUnisender(email, listId) {

    return new Promise(
        (resolve, reject) => {

            const name =
                document
                    .getElementById('name')
                    .value
                    .trim();

            const apiKey =
                window.UNISENDER_API_KEY;

            const apiEndpoint =
                'https://api.unisender.com/ru/api/subscribe';


            if (
                !email ||
                !listId ||
                !apiKey
            ) {
                resolve({
                    skipped: true
                });

                return;
            }


            const formData =
                new FormData();

            formData.append(
                'api_key',
                apiKey
            );

            formData.append(
                'list_ids',
                listId
            );

            formData.append(
                'fields[email]',
                email
            );

            formData.append(
                'fields[Name]',
                name
            );

            formData.append(
                'double_optin',
                '3'
            );


            fetch(
                apiEndpoint,
                {
                    method: 'POST',
                    body: formData
                }
            )
            .then(
                response =>
                    response.json()
            )
            .then(
                data => {

                    if (
                        data.result ||
                        data.skipped
                    ) {
                        resolve(data);
                    } else {
                        reject(
                            new Error(
                                data.error ||
                                'Unknown Unisender error'
                            )
                        );
                    }
                }
            )
            .catch(
                error => {
                    reject(error);
                }
            );
        }
    );
}


function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(
        email
    );
}


function getSelectedAmount() {

    const customRadio =
        document.getElementById(
            'value_choice_custom'
        );

    const customPriceValue =
        document.getElementById(
            'custom_price'
        ).value.trim();

    const checkedRadio =
        document.querySelector(
            "input[name='price']:checked"
        );


    if (customRadio.checked) {
        return parseFloat(
            customPriceValue
        );
    }


    if (checkedRadio) {
        return parseFloat(
            checkedRadio.value
        );
    }


    return NaN;
}


function updateRecurrentState() {

    const isMonthly =
        document
            .getElementById(
                'recurrent_choice_1'
            )
            .checked;

    const emailBlock =
        $('.email_field');

    const emailInput =
        document.getElementById(
            'email'
        );

    const confirmationBlock =
        $('#subscribe_confirmation_block');

    const confirmationInput =
        document.getElementById(
            'subscribe_confirmation'
        );

    const recurrentHint =
        $('#recurrent_hint');


    emailBlock.show();
    emailInput.required = true;


    if (isMonthly) {

        confirmationBlock
            .stop(true, true)
            .fadeIn(300);

        confirmationInput.required =
            true;

        recurrentHint
            .html(
                'Вы выбрали регулярное пожертвование. Выбранная сумма будет автоматически списываться с карты один раз в месяц.'
            )
            .fadeIn(300);

    } else {

        confirmationInput.required =
            false;

        confirmationInput.checked =
            false;

        confirmationBlock
            .stop(true, true)
            .hide();

        recurrentHint
            .html(
                'Вы выбрали разовое пожертвование. Выбранная сумма будет списана с карты один раз.'
            )
            .fadeIn(300);
    }
}


$(document).ready(function () {

    const amountRadios =
        document.querySelectorAll(
            'input[name="price"]'
        );

    const customInput =
        document.getElementById(
            'custom_price'
        );

    const customLabel =
        document.querySelector(
            '.custom-label'
        );

    const customRadio =
        document.getElementById(
            'value_choice_custom'
        );


    function activateCustom() {

        amountRadios.forEach(
            radio => {
                radio.checked = false;
            }
        );

        customRadio.checked = true;

        customLabel
            .classList
            .add('custom-active');
    }


    function deactivateCustom() {

        customLabel
            .classList
            .remove('custom-active');
    }


    function focusField(el) {

        if (!el) return;

        el.focus();

        if (
            typeof el.scrollIntoView ===
            'function'
        ) {
            el.scrollIntoView({
                behavior: 'smooth',
                block: 'center'
            });
        }
    }


    $(".recurrent_field input")
        .on(
            'change',
            function () {
                updateRecurrentState();
            }
        );


    customInput
        .addEventListener(
            'input',
            function () {

                const value =
                    this.value.trim();

                if (
                    value &&
                    parseFloat(value) > 0
                ) {
                    activateCustom();

                } else if (
                    customRadio.checked
                ) {
                    deactivateCustom();
                }
            }
        );


    customInput
        .addEventListener(
            'focus',
            function () {
                activateCustom();
            }
        );


    amountRadios.forEach(
        radio => {

            radio.addEventListener(
                'change',
                function () {

                    if (
                        this.id !==
                            'value_choice_custom' &&
                        this.checked
                    ) {
                        deactivateCustom();

                        customInput.value =
                            '';
                    }


                    if (
                        this.id ===
                            'value_choice_custom' &&
                        this.checked
                    ) {
                        customLabel
                            .classList
                            .add(
                                'custom-active'
                            );
                    }
                }
            );
        }
    );


    updateRecurrentState();


    $('#donation_form')
        .on(
            'submit',
            function (e) {

                e.preventDefault();


                const name =
                    $("#name")
                        .val()
                        .trim();

                const email =
                    $("#email")
                        .val()
                        .trim();

                const recurrent =
                    $(
                        "input[name='recurrent']:checked"
                    ).val();

                const isMonthly =
                    recurrent ===
                    'monthly';

                const privacyChecked =
                    $("#privacy")
                        .is(':checked');

                const subscribeChecked =
                    $("#subscribe_confirmation")
                        .is(':checked');

                const amount =
                    getSelectedAmount();


                if (!name) {
                    alert(
                        'Пожалуйста, укажите имя'
                    );

                    focusField(
                        document.getElementById(
                            'name'
                        )
                    );

                    return;
                }


                if (!email) {
                    alert(
                        'Пожалуйста, укажите email'
                    );

                    focusField(
                        document.getElementById(
                            'email'
                        )
                    );

                    return;
                }


                if (
                    !isValidEmail(email)
                ) {
                    alert(
                        'Пожалуйста, укажите корректный email'
                    );

                    focusField(
                        document.getElementById(
                            'email'
                        )
                    );

                    return;
                }


                if (
                    !amount ||
                    isNaN(amount) ||
                    amount <= 0
                ) {
                    alert(
                        'Пожалуйста, введите корректную сумму пожертвования'
                    );

                    focusField(
                        document.getElementById(
                            'custom_price'
                        )
                    );

                    return;
                }


                if (
                    !privacyChecked
                ) {
                    alert(
                        'Пожалуйста, подтвердите согласие с политикой конфиденциальности и офертой'
                    );

                    focusField(
                        document.getElementById(
                            'privacy'
                        )
                    );

                    return;
                }


                if (
                    isMonthly &&
                    !subscribeChecked
                ) {
                    alert(
                        'Пожалуйста, подтвердите, что пожертвование будет повторяться каждый месяц'
                    );

                    focusField(
                        document.getElementById(
                            'subscribe_confirmation'
                        )
                    );

                    return;
                }


                sendMetrikaGoal(
                    'donation_attempt',
                    {
                        order_price:
                            amount.toFixed(2),

                        currency:
                            'RUB',

                        recurrent:
                            recurrent
                    }
                );


                const options = {

                    widget_key:
                        window.MIXPLAT_WIDGET_KEY,

                    amount:
                        Math.round(
                            amount * 100
                        ),

                    description:
                        'Благотворительное пожертвование',

                    user_email:
                        email,

                    user_name:
                        name,

                    recurrent_payment:
                        isMonthly
                        ? 1
                        : 0
                };


                const M =
                    new Mixplat(options);


                M.build();


                M.setSuccessCallback(
                    async () => {

                        const finalAmount =
                            getSelectedAmount();

                        const finalRecurrent =
                            $(
                                "input[name='recurrent']:checked"
                            ).val();

                        const finalIsMonthly =
                            finalRecurrent ===
                            'monthly';

                        const finalEmail =
                            $("#email")
                                .val()
                                .trim();


                        const listId =
                            finalIsMonthly
                            ? window.UNISENDER_LIST_ID_MONTHLY
                            : window.UNISENDER_LIST_ID_ONETIME;


                        try {
                            await addToUnisender(
                                finalEmail,
                                listId
                            );

                        } catch (err) {
                            console.error(
                                '[Unisender] Ошибка:',
                                err
                            );
                        }


                        sendMetrikaGoal(
                            'donation_success',
                            {
                                order_price:
                                    finalAmount
                                        .toFixed(2),

                                currency:
                                    'RUB',

                                recurrent:
                                    finalRecurrent
                            }
                        );


                        window.dataLayer =
                            window.dataLayer || [];


                        window.dataLayer.push({

                            event:
                                'ecommerce_payment_success',

                            ecommerce: {

                                currencyCode:
                                    'RUB',

                                purchase: {

                                    actionField: {

                                        id:
                                            'donation_' +
                                            Date.now(),

                                        revenue:
                                            finalAmount
                                                .toFixed(2),

                                        affiliation:
                                            finalIsMonthly
                                            ? 'Пожертвование ежемесячное'
                                            : 'Пожертвование разовое'
                                    },


                                    products: [{
                                        name:
                                            finalIsMonthly
                                            ? 'Регулярное пожертвование'
                                            : 'Разовое пожертвование',

                                        id:
                                            finalIsMonthly
                                            ? 'donation_monthly'
                                            : 'donation_one_time',

                                        price:
                                            finalAmount
                                                .toFixed(2),

                                        quantity:
                                            1,

                                        variant:
                                            finalEmail,

                                        category:
                                            finalRecurrent,

                                        dimension1:
                                            document
                                                .getElementById(
                                                    'value_choice_custom'
                                                )
                                                .checked
                                    }]
                                }
                            }
                        });


                        await sleep(150);


                        window.location.href =
                            '/success';
                    }
                );


                M.setCloseCallback(
                    () => {

                        const finalRecurrent =
                            $(
                                "input[name='recurrent']:checked"
                            ).val();

                        const finalAmount =
                            getSelectedAmount();


                        if (
                            !isNaN(finalAmount) &&
                            finalAmount > 0
                        ) {
                            sendMetrikaGoal(
                                'donation_closed',
                                {
                                    order_price:
                                        finalAmount
                                            .toFixed(2),

                                    currency:
                                        'RUB',

                                    recurrent:
                                        finalRecurrent
                                }
                            );
                        }


                        window.location.href =
                            '/fail';
                    }
                );


                M.setFailCallback(
                    () => {

                        const finalRecurrent =
                            $(
                                "input[name='recurrent']:checked"
                            ).val();

                        const finalAmount =
                            getSelectedAmount();


                        if (
                            !isNaN(finalAmount) &&
                            finalAmount > 0
                        ) {
                            sendMetrikaGoal(
                                'donation_fail',
                                {
                                    order_price:
                                        finalAmount
                                            .toFixed(2),

                                    currency:
                                        'RUB',

                                    recurrent:
                                        finalRecurrent
                                }
                            );
                        }


                        window.location.href =
                            '/fail';
                    }
                );
            }
        );
});
</script>