← Назад к шаблонам На главную
Формы

Онлайн-оплата заказа

Блок оплаты для интернет-магазинов, сайтов услуг, клиник и онлайн-записи. Пользователь видит состав заказа, выбирает способ оплаты и получает понятное состояние перед отправкой платежа.

Сложность: Сложная HTML CSS JavaScript checkout UI payment methods validation order summary responsive layout
Онлайн-оплата

Оплата без лишних шагов

Пользователь видит заказ, выбирает способ оплаты и получает понятную проверку формы до отправки.

Контактные данные

Способ оплаты

Заполните данные — интерфейс покажет готовность к оплате.

<section class="pay-checkout">
    <div class="pay-checkout__head">
        <span class="pay-checkout__badge">Онлайн-оплата</span>
        <h2>Оплата без лишних шагов</h2>
        <p>
            Пользователь видит заказ, выбирает способ оплаты и получает понятную проверку формы до отправки.
        </p>
    </div>

    <div class="pay-checkout__grid">
        <form class="pay-form">
            <div class="pay-form__group">
                <h3>Контактные данные</h3>

                <label>
                    Имя
                    <input class="pay-name" type="text" placeholder="Александр">
                </label>

                <label>
                    Email
                    <input class="pay-email" type="email" placeholder="name@example.com">
                </label>
            </div>

            <div class="pay-form__group">
                <h3>Способ оплаты</h3>

                <div class="pay-methods">
                    <button class="pay-method active" type="button" data-method="Банковская карта">
                        <span>💳</span>
                        <strong>Карта</strong>
                    </button>

                    <button class="pay-method" type="button" data-method="СБП">
                        <span>⚡</span>
                        <strong>СБП</strong>
                    </button>

                    <button class="pay-method" type="button" data-method="Счёт для юрлица">
                        <span>📄</span>
                        <strong>Счёт</strong>
                    </button>
                </div>
            </div>

            <div class="pay-card-fields">
                <label>
                    Номер карты
                    <input class="pay-card-number" type="text" inputmode="numeric" maxlength="19" placeholder="0000 0000 0000 0000">
                </label>

                <div class="pay-card-fields__row">
                    <label>
                        Срок
                        <input class="pay-card-date" type="text" inputmode="numeric" maxlength="5" placeholder="MM/YY">
                    </label>

                    <label>
                        CVC
                        <input class="pay-card-cvc" type="text" inputmode="numeric" maxlength="3" placeholder="123">
                    </label>
                </div>
            </div>

            <label class="pay-agree">
                <input class="pay-agree-input" type="checkbox">
                <span>Согласен с условиями оплаты</span>
            </label>

            <button class="pay-submit" type="submit">
                Перейти к оплате
            </button>

            <p class="pay-form__message">
                Заполните данные — интерфейс покажет готовность к оплате.
            </p>
        </form>

        <aside class="pay-summary">
            <span class="pay-summary__eyebrow">Ваш заказ</span>

            <div class="pay-summary__item">
                <div>
                    <strong>Консультация специалиста</strong>
                    <span>1 встреча</span>
                </div>
                <b>2 500 ₽</b>
            </div>

            <div class="pay-summary__item">
                <div>
                    <strong>Индивидуальные рекомендации</strong>
                    <span>после консультации</span>
                </div>
                <b>900 ₽</b>
            </div>

            <div class="pay-summary__promo">
                <input class="pay-promo" type="text" placeholder="Промокод">
                <button class="pay-promo-button" type="button">OK</button>
            </div>

            <div class="pay-summary__total">
                <span>Итого</span>
                <strong><span class="pay-total">3 400</span> ₽</strong>
            </div>

            <div class="pay-summary__status">
                <span class="pay-status-dot"></span>
                <p class="pay-status-text">Ожидаем заполнения формы</p>
            </div>

            <div class="pay-summary__method">
                Способ оплаты: <strong class="pay-selected-method">Банковская карта</strong>
            </div>
        </aside>
    </div>
</section>
.pay-checkout {
    padding: 46px;
    border-radius: 28px;
    overflow: hidden;
    background:
        radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.30), transparent 32%),
        radial-gradient(circle at 88% 82%, rgba(34, 197, 94, 0.18), transparent 34%),
        linear-gradient(135deg, #020617, #0f172a);
}

.pay-checkout__head {
    max-width: 860px;
    margin-bottom: 34px;
}

.pay-checkout__badge {
    display: inline-flex;
    margin-bottom: 16px;
    padding: 8px 13px;
    border-radius: 999px;
    color: #bbf7d0;
    background: rgba(34, 197, 94, 0.12);
    border: 1px solid rgba(34, 197, 94, 0.35);
    font-size: 13px;
}

.pay-checkout__head h2 {
    margin: 0 0 16px;
    color: #ffffff;
    font-size: clamp(32px, 5vw, 56px);
    line-height: 1.02;
    letter-spacing: -0.05em;
}

.pay-checkout__head p {
    color: #cbd5e1;
    font-size: 17px;
    line-height: 1.7;
}

.pay-checkout__grid {
    display: grid;
    grid-template-columns: 1fr 0.85fr;
    gap: 26px;
    align-items: start;
}

.pay-form,
.pay-summary {
    border: 1px solid rgba(148, 163, 184, 0.28);
    border-radius: 26px;
    background: rgba(15, 23, 42, 0.78);
    box-shadow: 0 28px 80px rgba(2, 6, 23, 0.5);
}

.pay-form {
    display: grid;
    gap: 22px;
    padding: 26px;
}

.pay-form__group h3 {
    margin: 0 0 14px;
    color: #ffffff;
    font-size: 18px;
}

.pay-form label {
    display: grid;
    gap: 8px;
    margin-bottom: 14px;
    color: #dbeafe;
    font-size: 14px;
}

.pay-form input,
.pay-summary__promo input {
    width: 100%;
    border: 1px solid #334155;
    border-radius: 16px;
    padding: 13px 14px;
    color: #ffffff;
    background: #020617;
    outline: none;
}

.pay-form input:focus,
.pay-summary__promo input:focus {
    border-color: #60a5fa;
    box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.16);
}

.pay-methods {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
}

.pay-method {
    border: 1px solid #334155;
    border-radius: 18px;
    padding: 16px;
    color: #cbd5e1;
    background: #020617;
    cursor: pointer;
    transition: 0.25s;
}

.pay-method span,
.pay-method strong {
    display: block;
}

.pay-method span {
    margin-bottom: 8px;
    font-size: 24px;
}

.pay-method.active,
.pay-method:hover {
    border-color: #22c55e;
    background: rgba(34, 197, 94, 0.12);
    transform: translateY(-2px);
}

.pay-card-fields__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}

.pay-agree {
    display: flex !important;
    grid-template-columns: none !important;
    align-items: center;
    gap: 10px !important;
    margin: 0 !important;
}

.pay-agree input {
    width: auto;
    accent-color: #22c55e;
}

.pay-submit {
    border: 0;
    border-radius: 16px;
    padding: 15px 18px;
    color: #ffffff;
    background: linear-gradient(135deg, #16a34a, #2563eb);
    cursor: pointer;
    transition: 0.25s;
}

.pay-submit:hover {
    transform: translateY(-2px);
}

.pay-form__message {
    margin: 0;
    color: #94a3b8;
    font-size: 14px;
}

.pay-summary {
    position: sticky;
    top: 110px;
    padding: 28px;
    overflow: hidden;
}

.pay-summary::before {
    content: "";
    position: absolute;
    width: 260px;
    height: 260px;
    right: -110px;
    top: -110px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(34, 197, 94, 0.25), transparent 70%);
}

.pay-summary > * {
    position: relative;
    z-index: 2;
}

.pay-summary__eyebrow {
    display: inline-flex;
    margin-bottom: 18px;
    color: #94a3b8;
}

.pay-summary__item {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 0;
    border-bottom: 1px solid #334155;
}

.pay-summary__item strong,
.pay-summary__item b {
    color: #ffffff;
}

.pay-summary__item span {
    display: block;
    margin-top: 4px;
    color: #94a3b8;
    font-size: 13px;
}

.pay-summary__promo {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 10px;
    margin: 22px 0;
}

.pay-promo-button {
    border: 1px solid #334155;
    border-radius: 16px;
    padding: 0 16px;
    color: #ffffff;
    background: #020617;
    cursor: pointer;
}

.pay-summary__total {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
    margin: 24px 0;
    color: #cbd5e1;
}

.pay-summary__total strong {
    color: #ffffff;
    font-size: 38px;
    letter-spacing: -0.04em;
}

.pay-summary__status {
    display: flex;
    align-items: center;
    gap: 12px;
    border: 1px solid #334155;
    border-radius: 18px;
    padding: 14px;
    background: #020617;
}

.pay-status-dot {
    width: 12px;
    height: 12px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: #f59e0b;
    box-shadow: 0 0 20px rgba(245, 158, 11, 0.6);
}

.pay-status-text {
    margin: 0;
    color: #cbd5e1;
}

.pay-summary__method {
    margin-top: 16px;
    color: #94a3b8;
}

.pay-summary__method strong {
    color: #dbeafe;
}

.pay-summary.is-ready .pay-status-dot {
    background: #22c55e;
    box-shadow: 0 0 20px rgba(34, 197, 94, 0.7);
}

.pay-summary.is-error .pay-status-dot {
    background: #ef4444;
    box-shadow: 0 0 20px rgba(239, 68, 68, 0.7);
}

.pay-summary.is-updated {
    animation: payPulse 0.3s ease;
}

@keyframes payPulse {
    from {
        opacity: 0.76;
        transform: scale(0.985);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

@media (max-width: 900px) {
    .pay-checkout {
        padding: 26px 18px;
    }

    .pay-checkout__grid {
        grid-template-columns: 1fr;
    }

    .pay-summary {
        position: static;
    }
}

@media (max-width: 560px) {
    .pay-methods,
    .pay-card-fields__row {
        grid-template-columns: 1fr;
    }

    .pay-summary__total {
        align-items: flex-start;
        flex-direction: column;
    }
}
const payMethods = document.querySelectorAll(".pay-method");
const paySelectedMethod = document.querySelector(".pay-selected-method");
const payName = document.querySelector(".pay-name");
const payEmail = document.querySelector(".pay-email");
const payCardNumber = document.querySelector(".pay-card-number");
const payCardDate = document.querySelector(".pay-card-date");
const payCardCvc = document.querySelector(".pay-card-cvc");
const payAgree = document.querySelector(".pay-agree-input");
const payForm = document.querySelector(".pay-form");
const payMessage = document.querySelector(".pay-form__message");
const paySummary = document.querySelector(".pay-summary");
const payStatusText = document.querySelector(".pay-status-text");
const payPromo = document.querySelector(".pay-promo");
const payPromoButton = document.querySelector(".pay-promo-button");
const payTotal = document.querySelector(".pay-total");

let currentTotal = 3400;
let discountApplied = false;

function updatePayStatus() {
    const nameReady = payName.value.trim().length >= 2;
    const emailReady = payEmail.value.includes("@") && payEmail.value.includes(".");
    const cardReady = payCardNumber.value.replace(/\s/g, "").length >= 16 &&
        payCardDate.value.length === 5 &&
        payCardCvc.value.length === 3;

    const ready = nameReady && emailReady && cardReady && payAgree.checked;

    paySummary.classList.remove("is-ready", "is-error", "is-updated");
    void paySummary.offsetWidth;
    paySummary.classList.add("is-updated");

    if (ready) {
        paySummary.classList.add("is-ready");
        payStatusText.textContent = "Готово к оплате";
        payMessage.textContent = "Данные выглядят корректно. Можно переходить к оплате.";
        payMessage.style.color = "#86efac";
    } else {
        payStatusText.textContent = "Ожидаем заполнения формы";
        payMessage.textContent = "Заполните данные — интерфейс покажет готовность к оплате.";
        payMessage.style.color = "#94a3b8";
    }
}

function formatCardNumber(value) {
    return value.replace(/\D/g, "").slice(0, 16).replace(/(.{4})/g, "$1 ").trim();
}

function formatCardDate(value) {
    const clean = value.replace(/\D/g, "").slice(0, 4);

    if (clean.length <= 2) {
        return clean;
    }

    return clean.slice(0, 2) + "/" + clean.slice(2);
}

payMethods.forEach(function (method) {
    method.addEventListener("click", function () {
        payMethods.forEach(item => item.classList.remove("active"));
        method.classList.add("active");
        paySelectedMethod.textContent = method.dataset.method;
        updatePayStatus();
    });
});

[payName, payEmail, payAgree].forEach(function (field) {
    field.addEventListener("input", updatePayStatus);
    field.addEventListener("change", updatePayStatus);
});

payCardNumber.addEventListener("input", function () {
    payCardNumber.value = formatCardNumber(payCardNumber.value);
    updatePayStatus();
});

payCardDate.addEventListener("input", function () {
    payCardDate.value = formatCardDate(payCardDate.value);
    updatePayStatus();
});

payCardCvc.addEventListener("input", function () {
    payCardCvc.value = payCardCvc.value.replace(/\D/g, "").slice(0, 3);
    updatePayStatus();
});

payPromoButton.addEventListener("click", function () {
    if (discountApplied) {
        return;
    }

    if (payPromo.value.trim().toLowerCase() === "start") {
        currentTotal -= 500;
        discountApplied = true;
        payTotal.textContent = new Intl.NumberFormat("ru-RU").format(currentTotal);
        payPromoButton.textContent = "−500";
        payPromoButton.style.color = "#86efac";
    }
});

payForm.addEventListener("submit", function (event) {
    event.preventDefault();
    updatePayStatus();

    if (!paySummary.classList.contains("is-ready")) {
        paySummary.classList.add("is-error");
        payStatusText.textContent = "Проверьте данные";
        payMessage.textContent = "Не все поля заполнены корректно.";
        payMessage.style.color = "#fca5a5";
    }
});

updatePayStatus();
🚀 Рассчитать проект