Модификации

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

2026-08-12 08:56 Формы пожертвования

Инструкция

Это готовая связка для приёма пожертвований на Tilda через CloudPayments. Форма поддерживает разовые и ежемесячные платежи, готовые и произвольные суммы, проверку обязательных полей и отдельное подтверждение регулярного списания.

После успешной оплаты данные донора можно автоматически передавать в нужный список UniSender, а действия пользователя и сам платёж — фиксировать в Яндекс Метрике и электронной коммерции. Внешний вид формы можно адаптировать под стиль проекта, не вмешиваясь в основную платёжную логику.
  1. В самом начале кода найдите блок «НАСТРОЙКИ ИНТЕГРАЦИЙ», вырежьте его из основного кода и вставьте в Tilda в «Настройки сайта → Ещё → HTML-код для вставки внутрь HEAD». Остальной код целиком вставьте на страницу в отдельный блок T123 «HTML-код».
  2. В блоке для HEAD замените CLOUD_WIDGET_KEY на публичный ID терминала CloudPayments, 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-accent-soft и --nf-accent-text. Скругления карточки и полей задаются через --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-проверку amount <= 0 на нужный минимум.
  7. Поля #name и #email используются платёжной логикой CloudPayments и передачей данных в UniSender, поэтому их ID не меняйте. Тексты placeholder можно заменить на свои.
  8. В функции updateRecurrentState() можно заменить тексты, которые показываются после выбора разового или ежемесячного пожертвования. Саму проверку isMonthly, обработчики переключателей #recurrent_choice_1 и #recurrent_choice_2, блок #recurrent_hint и подтверждение #subscribe_confirmation не удаляйте — они отвечают за смену текста и обязательное подтверждение регулярного списания.
  9. В ссылках href="/policy" и href="/oferta" укажите адреса своих юридических документов, а текст основной кнопки при необходимости измените в value="ПОМОЧЬ".
  10. В объекте intentParams при необходимости измените description для разового и регулярного пожертвования. Остальные параметры CloudPayments без необходимости не меняйте: publicTerminalId, paymentSchema, amount, currency, externalId, receiptEmail, userInfo.accountId и другие параметры участвуют в создании платежа.
  11. Для ежемесячного пожертвования код добавляет в параметры CloudPayments объект recurrent со значениями interval: 'Month' и period: 1. Для разового платежа recurrent не передаётся. Не переносите его в общую часть intentParams, иначе логика разовых и регулярных платежей изменится.
  12. После успешной оплаты разовый донор автоматически отправляется в список UNISENDER_LIST_ID_ONETIME, а регулярный — в UNISENDER_LIST_ID_MONTHLY. Убедитесь, что в HEAD указаны существующие ID нужных списков UniSender.
  13. После успешного платежа пользователь перенаправляется на /success, а после ошибки или закрытия платёжного окна — на /fail. Замените эти адреса на страницы успешного и неуспешного платежа своего сайта.
  14. Не меняйте без необходимости технические значения name="price", name="recurrent", value="one-time", value="monthly", ID #recurrent_choice_1, #recurrent_choice_2, #recurrent_hint, #subscribe_confirmation, #custom_price, #name, #email, а также платёжную логику widget.start(), paymentSchema: 'Single', userInfo.accountId и добавление recurrent только для ежемесячного платежа.
  15. Перед публикацией обязательно проверьте переключение «Помочь разово» / «Помогать ежемесячно»: при смене варианта должен меняться текст в #recurrent_hint, а при ежемесячном пожертвовании должен дополнительно появляться обязательный чекбокс #subscribe_confirmation. Затем проверьте все готовые суммы, #custom_price, поля #name и #email, разовый и регулярный платёж CloudPayments, успешную оплату, ошибку и закрытие платёжного окна.
  16. После успешной оплаты отдельно проверьте попадание контакта в правильный список UniSender, цель donation_success и событие ecommerce_payment_success в dataLayer, а также переход на /success. Для остальных действий проверьте цели donation_visible, donation_attempt, donation_closed и donation_fail.
Рекомендации:
Настройки в HEAD:
  • CLOUD_WIDGET_KEY — ключ Сloud Payments.
  • 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 с CloudPayments, UniSender, Яндекс Метрикой и ecommerce. Источник: bulgkate.ru -->

<!-- =========================================================
     НАСТРОЙКИ ИНТЕГРАЦИЙ

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

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

<script src="https://widget.cloudpayments.ru/bundles/cloudpayments.js"></script>

<script>
    window.CLOUD_WIDGET_KEY = 'YOUR_CLOUD_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: #f7f5fa;
    --nf-card: #ffffff;

    --nf-text: #302b36;
    --nf-muted: #625b6b;

    --nf-field: #f3f0f7;
    --nf-border: #ddd7e5;

    --nf-accent: #6750a4;
    --nf-accent-hover: #57418f;
    --nf-accent-soft: #eee9f7;
    --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(54, 43, 73, 0.07),
        0 3px 10px rgba(54, 43, 73, 0.035);
}

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

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


/* =========================================================
   RADIO
   ========================================================= */

.nf-support input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    opacity: 0;
    pointer-events: 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 16px;
    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(54, 43, 73, 0.09);
}


/* =========================================================
   ТЕКСТ ПРО ТИП ПОЖЕРТВОВАНИЯ
   ========================================================= */

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

    margin: 0 0 28px;
    padding: 0 2px;

    color: #554e5e !important;

    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;
}

@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.72;
}

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

.nf-support .custom-label.custom-active .custom-input::placeholder {
    color: var(--nf-accent-text);
    opacity: 0.9;
}


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

.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: #756d7d;
    opacity: 1;
}

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

    box-shadow:
        0 0 0 3px rgba(103, 80, 164, 0.12);
}


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

.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: #59418e !important;

    font-weight: 500;

    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(72, 52, 118, 0.19);
    }
}

.nf-support #donation_form .donate-button:focus-visible {
    outline: 3px solid rgba(103, 80, 164, 0.25);
    outline-offset: 3px;
}

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


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

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

    .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: 14px;
    }

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

        padding: 0 8px;

        font-size: 14px;
    }

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

        margin-bottom: 24px;

        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>


<script>
(function () {

    /* =========================================================
       ЭЛЕМЕНТЫ
       ========================================================= */

    var form =
        document.getElementById(
            'donation_form'
        );

    if (!form) {
        return;
    }


    var recurrentOneTime =
        document.getElementById(
            'recurrent_choice_2'
        );

    var recurrentMonthly =
        document.getElementById(
            'recurrent_choice_1'
        );

    var recurrentHint =
        document.getElementById(
            'recurrent_hint'
        );

    var subscribeBlock =
        document.getElementById(
            'subscribe_confirmation_block'
        );

    var subscribeConfirmation =
        document.getElementById(
            'subscribe_confirmation'
        );

    var nameInput =
        document.getElementById(
            'name'
        );

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

    var privacyInput =
        document.getElementById(
            'privacy'
        );

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

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

    var customLabel =
        form.querySelector(
            '.custom-label'
        );

    var amountRadios =
        form.querySelectorAll(
            'input[name="price"]'
        );


    /* =========================================================
       ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
       ========================================================= */

    function sleep(ms) {

        return new Promise(
            function (resolve) {
                setTimeout(
                    resolve,
                    ms
                );
            }
        );
    }


    function uniqueID() {

        return (
            'donation_' +
            Date.now() +
            '_' +
            Math.random()
                .toString(36)
                .slice(2, 9)
        );
    }


    function isValidEmail(email) {

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


    function focusField(element) {

        if (!element) {
            return;
        }

        element.focus();

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


    function sendMetrikaGoal(
        goalName,
        params
    ) {

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


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

       ЛОГИКА ТА ЖЕ, ЧТО В MIXPLAT:
       читаем выбранный radio и сразу обновляем интерфейс.
       ========================================================= */

    function updateRecurrentState() {

        var isMonthly =
            recurrentMonthly.checked;


        if (isMonthly) {

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

            subscribeBlock.style.display =
                'flex';

            subscribeConfirmation.required =
                true;

        } else {

            recurrentHint.textContent =
                'Вы выбрали разовое пожертвование. Выбранная сумма будет списана с карты один раз.';

            subscribeBlock.style.display =
                'none';

            subscribeConfirmation.required =
                false;

            subscribeConfirmation.checked =
                false;
        }
    }


    recurrentOneTime.addEventListener(
        'change',
        updateRecurrentState
    );


    recurrentMonthly.addEventListener(
        'change',
        updateRecurrentState
    );


    /*
     * Синхронизируем состояние при первой загрузке.
     */

    updateRecurrentState();


    /* =========================================================
       ПРОИЗВОЛЬНАЯ СУММА
       ========================================================= */

    function activateCustomAmount() {

        amountRadios.forEach(
            function (radio) {

                if (
                    radio !==
                    customRadio
                ) {
                    radio.checked =
                        false;
                }
            }
        );


        customRadio.checked =
            true;

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


    function deactivateCustomAmount() {

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


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

            activateCustomAmount();
        }
    );


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

            if (
                this.value.trim() !== ''
            ) {
                activateCustomAmount();
            }
        }
    );


    amountRadios.forEach(
        function (radio) {

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

                    if (
                        this ===
                        customRadio
                    ) {

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

                        return;
                    }


                    if (this.checked) {

                        customInput.value =
                            '';

                        deactivateCustomAmount();
                    }
                }
            );
        }
    );


    /* =========================================================
       ПОЛУЧЕНИЕ СУММЫ
       ========================================================= */

    function getSelectedAmount() {

        if (
            customRadio.checked
        ) {

            return parseFloat(
                customInput.value
            );
        }


        var checkedAmount =
            form.querySelector(
                'input[name="price"]:checked'
            );


        if (!checkedAmount) {

            return NaN;
        }


        return parseFloat(
            checkedAmount.value
        );
    }


    /* =========================================================
       UNISENDER
       ========================================================= */

    function addToUnisender(
        email,
        listId,
        name
    ) {

        return new Promise(
            function (
                resolve,
                reject
            ) {

                var apiKey =
                    window.UNISENDER_API_KEY;


                if (
                    !apiKey ||
                    !listId ||
                    !email
                ) {

                    resolve({
                        skipped: true
                    });

                    return;
                }


                var 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(
                    'https://api.unisender.com/ru/api/subscribe',
                    {
                        method: 'POST',
                        body: formData
                    }
                )
                .then(
                    function (response) {

                        return response.json();
                    }
                )
                .then(
                    function (data) {

                        if (data.result) {

                            resolve(data);

                        } else {

                            reject(
                                new Error(
                                    data.error ||
                                    'UniSender error'
                                )
                            );
                        }
                    }
                )
                .catch(
                    function (error) {

                        reject(error);
                    }
                );
            }
        );
    }


    /* =========================================================
       DONATION_VISIBLE
       ========================================================= */

    var visibilitySent =
        false;


    function sendVisibilityGoal() {

        if (visibilitySent) {
            return;
        }


        visibilitySent =
            true;


        sendMetrikaGoal(
            'donation_visible'
        );
    }


    if (
        'IntersectionObserver' in window
    ) {

        var observer =
            new IntersectionObserver(
                function (entries) {

                    entries.forEach(
                        function (entry) {

                            if (
                                entry.isIntersecting &&
                                entry.intersectionRatio >=
                                    0.25
                            ) {

                                sendVisibilityGoal();

                                observer.disconnect();
                            }
                        }
                    );
                },
                {
                    threshold: 0.25
                }
            );


        observer.observe(form);

    } else {

        sendVisibilityGoal();
    }


    /* =========================================================
       ОТПРАВКА ФОРМЫ
       ========================================================= */

    form.addEventListener(
        'submit',
        function (event) {

            event.preventDefault();


            var name =
                nameInput.value.trim();

            var email =
                emailInput.value.trim();

            var amount =
                getSelectedAmount();

            var isMonthly =
                recurrentMonthly.checked;

            var donationType =
                isMonthly
                ? 'monthly'
                : 'one-time';


            /* =================================================
               ВАЛИДАЦИЯ
               ================================================= */

            if (!name) {

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

                focusField(
                    nameInput
                );

                return;
            }


            if (!email) {

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

                focusField(
                    emailInput
                );

                return;
            }


            if (
                !isValidEmail(email)
            ) {

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

                focusField(
                    emailInput
                );

                return;
            }


            if (
                !Number.isFinite(amount) ||
                amount <= 0
            ) {

                alert(
                    'Пожалуйста, укажите корректную сумму пожертвования'
                );

                if (
                    customRadio.checked
                ) {

                    focusField(
                        customInput
                    );
                }

                return;
            }


            if (
                !privacyInput.checked
            ) {

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

                focusField(
                    privacyInput
                );

                return;
            }


            if (
                isMonthly &&
                !subscribeConfirmation.checked
            ) {

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

                focusField(
                    subscribeConfirmation
                );

                return;
            }


            /* =================================================
               ПРОВЕРКА CLOUDPAYMENTS
               ================================================= */

            if (
                !window.CLOUD_WIDGET_KEY
            ) {

                console.error(
                    '[CloudPayments] Не указан CLOUD_WIDGET_KEY'
                );

                alert(
                    'Платёжная форма не настроена.'
                );

                return;
            }


            if (
                typeof window.cp ===
                    'undefined' ||
                typeof window.cp.CloudPayments ===
                    'undefined'
            ) {

                console.error(
                    '[CloudPayments] Скрипт CloudPayments не загружен'
                );

                alert(
                    'Не удалось загрузить платёжную форму. Пожалуйста, попробуйте ещё раз.'
                );

                return;
            }


            /* =================================================
               DONATION_ATTEMPT
               ================================================= */

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

                    currency:
                        'RUB',

                    recurrent:
                        donationType
                }
            );


            /* =================================================
               CLOUDPAYMENTS
               ================================================= */

            var orderId =
                uniqueID();


            var widget =
                new window.cp.CloudPayments();


            var intentParams = {

                publicTerminalId:
                    window.CLOUD_WIDGET_KEY,

                paymentSchema:
                    'Single',

                description:
                    isMonthly
                    ? 'Регулярное благотворительное пожертвование'
                    : 'Благотворительное пожертвование',

                amount:
                    amount,

                currency:
                    'RUB',

                culture:
                    'ru-RU',

                skin:
                    'classic',

                externalId:
                    orderId,

                receiptEmail:
                    email,

                emailBehavior:
                    'Hidden',

                userInfo: {

                    accountId:
                        email,

                    fullName:
                        name,

                    email:
                        email
                },

                metadata: {

                    donationType:
                        donationType,

                    source:
                        'tilda'
                }
            };


            /*
             * ВАЖНО:
             *
             * Только для ежемесячного пожертвования
             * передаём recurrent.
             *
             * Для разового его вообще нет.
             */

            if (isMonthly) {

                intentParams.recurrent = {

                    interval:
                        'Month',

                    period:
                        1
                };
            }


            var paymentState =
                'processing';


            /* =================================================
               ЗАКРЫТИЕ CLOUDPAYMENTS БЕЗ ОПЛАТЫ
               ================================================= */

            widget.oncomplete =
                function (result) {

                    if (
                        !result ||
                        result.type !==
                            'cancel'
                    ) {

                        return;
                    }


                    if (
                        paymentState !==
                            'processing'
                    ) {

                        return;
                    }


                    paymentState =
                        'closed';


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

                            currency:
                                'RUB',

                            recurrent:
                                donationType
                        }
                    );


                    window.setTimeout(
                        function () {

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


            /* =================================================
               ЗАПУСК CLOUDPAYMENTS
               ================================================= */

            widget
                .start(
                    intentParams
                )

                /* =================================================
                   УСПЕШНЫЙ ПЛАТЁЖ
                   ================================================= */

                .then(
                    async function (
                        widgetResult
                    ) {

                        if (
                            paymentState !==
                                'processing'
                        ) {

                            return;
                        }


                        paymentState =
                            'success';


                        var listId =
                            isMonthly
                            ? window.UNISENDER_LIST_ID_MONTHLY
                            : window.UNISENDER_LIST_ID_ONETIME;


                        /*
                         * UniSender не влияет
                         * на статус уже успешного платежа.
                         */

                        try {

                            await addToUnisender(
                                email,
                                listId,
                                name
                            );

                        } catch (
                            unisenderError
                        ) {

                            console.error(
                                '[UniSender] Ошибка:',
                                unisenderError
                            );
                        }


                        /* =================================================
                           DONATION_SUCCESS
                           ================================================= */

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

                                currency:
                                    'RUB',

                                recurrent:
                                    donationType
                            }
                        );


                        /* =================================================
                           ECOMMERCE
                           ================================================= */

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


                        window.dataLayer.push({

                            event:
                                'ecommerce_payment_success',

                            ecommerce: {

                                currencyCode:
                                    'RUB',

                                purchase: {

                                    actionField: {

                                        id:
                                            orderId,

                                        revenue:
                                            amount.toFixed(2),

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


                                    products: [

                                        {

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

                                            id:
                                                isMonthly
                                                ? 'donation_monthly'
                                                : 'donation_one_time',

                                            price:
                                                amount.toFixed(2),

                                            quantity:
                                                1,

                                            variant:
                                                email,

                                            category:
                                                donationType,

                                            dimension1:
                                                customRadio.checked
                                        }

                                    ]
                                }
                            }
                        });


                        console.log(
                            '[CloudPayments] Успешный платёж:',
                            widgetResult
                        );


                        await sleep(
                            150
                        );


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


                /* =================================================
                   НЕУСПЕШНЫЙ ПЛАТЁЖ
                   ================================================= */

                .catch(
                    function (error) {

                        /*
                         * Если пользователь просто
                         * закрыл виджет, событие уже
                         * обработано через oncomplete.
                         */

                        if (
                            paymentState !==
                                'processing'
                        ) {

                            return;
                        }


                        paymentState =
                            'fail';


                        console.error(
                            '[CloudPayments] Ошибка платежа:',
                            error
                        );


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

                                currency:
                                    'RUB',

                                recurrent:
                                    donationType
                            }
                        );


                        window.setTimeout(
                            function () {

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

})();
</script>